مقاله فنی

audit encryption و permissionهای PDF در Delphi با PDF Library for Delphi

یک permission flag یک مکانیزم امنیتی نیست. بیت می‌گوید «no copying» داخل همان dictionary /Encrypt زندگی می‌کند که cryptography، که به آن هوایی از enforcement می‌دهد که ندارد، و لحظه‌ای که آن دو را به‌عنوان یک چیز در نظر بگیرید audit شما شروع به تولید پاسخ‌های اشتباه می‌کند. تنها سوال ارزش پرسیدن درباره یک PDF این نیست که «آیا encrypted است.» خاص‌تر و سخت‌تر است: کدام algorithm، کدام security handler revision، کدام از دو password set شده، کدام permission bitها claim شده‌اند، و کدام بخش‌های file را encryption واقعاً touch می‌کند. یک file می‌تواند به‌صورت رسمی encrypted و عملاً باز باشد. می‌تواند از خوانده‌شدن امتناع کند و باز هم metadata خود را در plaintext بگذارد. می‌تواند print را در یک flag قفل کند که هر viewer آزاد است نادیده بگیرد. audit کردن یک PDF یعنی جدا کردن همهٔ آن‌ها، و PDF Library for Delphi، موتور PDF شرکت losLab برای Delphi و C++Builder، هر یک از آن‌ها را هم از طریق یک integer-handle API و هم یک لایهٔ class typed در دسترس قرار می‌دهد

آنچه dictionary /Encrypt واقعاً ضبط می‌کند

ISO 32000-1 §7.6 امنیت document را از طریق چند dictionary entry تعریف می‌کند، و PDF Library for Delphi آن‌ها را یک‌به‌یک در record TPDFEncryption بازتاب می‌دهد. filter version V و revision R خانوادهٔ algorithm را انتخاب می‌کنند. Length اندازهٔ key را حمل می‌کند. permission bitها در P می‌نشینند، validation stringهای owner password و user password در O و U (با OE و UE که برای AES-256 اضافه شده‌اند)، یک flag EncryptMetadata در کنار آن می‌رود، و سه field دیگر crypt filterهایی که به‌ترتیب به stringها، streamها و embedded fileها اعمال می‌شوند را نام‌گذاری می‌کنند

ارزش این record در این است که هیچ‌چیز را برای شما تفسیر نمی‌کند. dictionary خام را برمی‌گرداند و نتیجه‌گیری را به شما می‌سپارد، که دقیقاً همان چیزی است که یک audit نیاز دارد. مورد plaintext-inside-encrypted در StringFilterIdentity و StreamFilterIdentity ظاهر می‌شود: وقتی هر کدام True باشند، دادهٔ مربوطه از filter Identity دست‌نخورده عبور می‌کند، مهم‌نمی‌کند status encrypted document چه گزارش می‌دهد. یک scanner که در «یک dictionary /Encrypt موجود است» متوقف شود، چنین fileای را زمانی که stringها و streamهایش در clear نشسته‌اند protected می‌نامد. همان nuance بر metadata حاکم است. وقتی EncryptMetadata False باشد packet XMP برای هر indexerای قابل‌خواندن می‌ماند در حالی که content page این‌طور نیست، که لحظه‌ای ارزش دانستن دارد که ruleهای routing شما روی یک field title یا author کار می‌کنند

نمودار PDF Library for Delphi از فیلدهای دیکشنری /Encrypt در PDF نگاشت‌شده به ویژگی‌های ممیزی TPDFEncryption شامل تله‌های فیلتر رمز Identity
هر مدخل /Encrypt به فیلدی در TPDFEncryption نگاشت می‌شود و پرچم‌های فیلتر Identity آشکار می‌کنند کدام رشته‌ها و جریان‌ها یا فراداده‌ها مستقل از وضعیت رمزگذاری خوانا می‌مانند

یک security probe کوتاه با flat API

برای بیشتر pipelineها، چهار flat call سوالات روزمره را پاسخ می‌دهند. LoadFromFile در صورت موفقیت ۱ برمی‌گرداند، و یک‌بار document باز شود، encryption inspectorها روی decrypted state آن گزارش می‌دهند:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', UserPassword) <> 1 then
      raise Exception.Create('Open failed: wrong password or damaged file');
    Writeln('status    : ', PDF.EncryptionStatus);     // decrypted / encrypted / unknown
    Writeln('algorithm : ', PDF.EncryptionAlgorithm);  // RC4 vs AES family
    Writeln('strength  : ', PDF.EncryptionStrength);   // کلاس طول کلید
    Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
  finally
    PDF.Free;
  end;
end;

CheckPassword بیشتر از signature یک‌خطی‌اش که نشان می‌دهد اهمیت دارد. PDF دو password با قدر نابرابر تعریف می‌کند. user password برای باز کردن file به‌کل لازم است. owner password حق کامل می‌دهد و هر permission bity را override می‌کند. byteهای روی disk به‌هرحال یکی هستند، اما یک session باز‌شده تحت owner password کارهایی می‌تواند بکند که session user-password نمی‌تواند، پس audit‌ای که کدام credential ارائه شده را ضبط نکند، نیمی از حقیقت را ضبط می‌کند. لایهٔ class این تمایز را قابل query می‌کند. TPDFDocument.HasUserPassword و HasOwnerPassword گزارش می‌دهند file چه نیاز دارد، در حالی که IsUserPassword و IsOwnerPassword گزارش می‌دهند کدام password واقعاً session فعلی را باز کرده است. این fact را log کنید. هرگز مقادیر password خود را log نکنید

نردبان Strength، جایی که «AES-256» دو معنا دارد

functionهای flat Encrypt و EncryptFile یک integer Strength با پنج مقدار معنادار می‌گیرند: ۰ برای RC4 با ۴۰بیت، ۱ برای RC4 با ۱۲۸بیت، ۲ برای AES با ۱۲۸بیت قابل‌خواندن از Acrobat 7، ۳ برای AES با ۲۵۶بیت همان‌طور که با Acrobat 9 معرفی شد، و ۴ برای AES با ۲۵۶بیت همان‌طور که Acrobat X و بعد از آن نیاز دارند

قسمت جالب این است که ۳ و ۴ هر دو AES-256 label شده‌اند و یک scheme یکی نیستند. Strength 3 به security handler revision 5 نگاشت می‌شود، یک design موقتی که Acrobat 9 منتشر کرد و ISO هرگز نپذیرفت. Strength 4 به revision 6 نگاشت می‌شود، که key-derivation function آن در ISO 32000-2 harden و standard شد. برای documentای که امروز می‌سازید دلیلی برای انتخاب ۳ به‌جای ۴ نیست. برای یک audit این فاصله تعیین‌کننده است: یک policy که می‌خواند «AES-256 مطابق ISO 32000-2» فقط با R6 برآورده می‌شود، و یک file R5 که خود را AES-256 می‌نامد آن policy را وقتی از یک strength check ساده‌لوحانه عبور می‌کند، شکست می‌دهد. لایهٔ class این دو را با name جدا نگه می‌دارد، esAES256Bit برای R5 در برابر esAES256BitAcroX برای R6، و property EncryptionAcroX سوال revision را با یک boolean واحد پاسخ می‌دهد

نردبان Strength رمزنگاری PDF از RC4 چهل‌بیتی تا AES-256 با revision 5 در مقایسه با revision 6 برای ممیزی در Delphi
قدرت‌های 3 و 4 هر دو AES-256 نامیده می‌شوند، اما فقط بازبینی 6 سیاست ISO 32000-2 را برآورده می‌کند، پس ممیزی‌ها باید بازبینیِ هندلر را ثبت کنند نه فقط برچسب را

permission bitها و fine print طول-key آن‌ها

EncodePermissions هشت flag را در integerای که Encrypt و EncryptFile انتظار دارند pack می‌کند. print، copy، change و add-notes set پایه را می‌سازند؛ fill-fields، copy-for-accessibility، assemble و full-quality print set گسترش‌یافته را می‌سازند. fine print، که demo encryption خود کتابخانه صریح بیان می‌کند، این است که چهار تای گسترش‌یافته فقط در strength ۱۲۸بیت و بالاتر اثر می‌کنند. flag full-quality-print تحت همان rule می‌آید: آن را clear کنید تا print با resolution پایین اجبار شود و یک document ۴۰بیتی شما را نادیده می‌گیرد، چون آن downgrade هم نیاز به encryption ۱۲۸بیتی یا قوی‌تر دارد. یک policy «low-resolution print only» را در یک file ۴۰بیتی encode کنید و هر viewer به‌هرحال با کیفیت کامل print می‌کند

سوال عمیق‌تر این است که چه کسی این bitها را enforce می‌کند، و پاسخ هیچ‌کسی نیست که بتوانید به او اعتماد کنید. permissionها instruction به readerهای conforming هستند، نه restrictionهای cryptographic. decryption key چه copy مجاز باشد چه denied یکسان است، پس یک permission set قفل‌شده فقط viewerهای صادق را صادق نگه می‌دارد. readerای که بخواهد bitها را نادیده بگیرد با هیچ مانع cryptographic روبرو نمی‌شود. اگر تعهد جلوگیری از extraction به‌جای discouraging آن است، file به یک user password نیاز دارد و workflow به controlهای در سطح process دور آن، و یک گزارش audit باید نام ببرد که file تحت کدام یک از دو رژیم است به‌جای اینکه یک permission flag را به‌عنوان یک lock در نظر بگیرد

set کردن policy و اثبات اینکه چسبیده

اعمال encryption به fileهای موجود نیازی ندارد آن‌ها را در object tree load کند. EncryptFile input را به output در یک call واحد پردازش می‌کند، و حلقهٔ audit نتیجه را باز می‌کند تا تایید کند چه چیزی روی disk land شده. demo encryption شده از همان شکل write-then-read-back پیروی می‌کند:

var
  PDF: TPDFlib;
  R: Integer;
begin
  PDF := TPDFlib.Create;
  try
    R := PDF.EncryptFile('in.pdf', 'out.pdf', 'owner-secret', 'user-secret', 4,
      PDF.EncodePermissions(1, 0, 0, 0,    // print allowed; copy/change/notes denied
                            0, 0, 0, 1));  // مجموعه گسترده: فقط چاپ با کیفیت کامل
    if (R = 1) and (PDF.LoadFromFile('out.pdf', 'user-secret') = 1) then
    begin
      Writeln('algorithm = ', PDF.EncryptionAlgorithm);
      Writeln('strength  = ', PDF.EncryptionStrength);
      Writeln('owner pw accepted: ', PDF.CheckPassword('owner-secret'));
    end;
  finally
    PDF.Free;
  end;
end;

تیم‌هایی که در لایهٔ document کار می‌کنند همان عملیات را با setهای typed به‌جای bit packing می‌گیرند، که از code review با کمترین squinting جان سالم به در می‌برد:

if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,
  [ppCanPrint], [ppCanPrintFull]) then
  raise Exception.Create('Encryption failed');

به‌هرحال، step read-back یک تشریفات اختیاری نیست. اشتباهات deployment را می‌گیرد که در غیر این صورت ماه‌ها بعد روی machine یک مشتری ظاهر می‌شوند: یک build قدیمی کتابخانه که strength درخواست‌شده را بی‌صدا downgrade می‌کند، یک output path که به‌خاطر read-only بودن directory هرگز نوشته نشد، یک integer permission که argumentهایش به ترتیب اشتباه رفتند. هر سه از یک smoke test محلی عبور می‌کنند و در field شکست می‌خورند، و باز کردن دوبارهٔ output هر یک از آن‌ها را به یک exception تبدیل می‌کند که در طول runی که file را ساخت دیده می‌شود. GetEncryptionFingerprint یک مقدار فشرده برمی‌گرداند که می‌توانید با job record ذخیره کنید، تا یک مقایسهٔ بعدی بتواند بگوید آیا دو output پیکربندی encryption یکسانی دارند بدون اینکه هیچ‌کدام باز شوند

false positiveهای audit ارزش coding برایشان

چند pattern به‌طرز قابل‌اعتمادی scannerهای امنیتی را به نتیجهٔ اشتباه می‌رانند، و هر کدام از فروپاشی کردن یک سوال چندبخشی به یک پاسخ بله-یا-خیر می‌آید. crypt filter Identity تمیزترین مثال است. یک dictionary /Encrypt موجود است، file به‌عنوان encrypted گزارش می‌دهد، و باز هم stringها و streamها از filter Identity دست‌نخورده عبور می‌کنند، پس content واقعی plaintext است. خواندن StringFilterIdentity و StreamFilterIdentity قبل از اعلام کردن هر چیز به‌عنوان protected راه‌حل است

split metadata subtleتر است. EncryptMetadata می‌تواند در هر دو جهت با بقیهٔ document مخالفت کند، و یک file encrypted با یک packet XMP قابل‌خواندن جا بگذارد، یا کمتر رایج، برعکس. «file encrypted است» درباره اینکه آیا metadata آن هم هست چیزی نمی‌گوید، که لحظه‌ای مهم می‌شود که یک indexer یا rule routing به سراغ title می‌رود. embedded fileها یک محور سوم اضافه می‌کنند: PDF اجازه می‌دهد یک crypt filter اختصاصی فقط برای attachmentها باشد، پس attachmentها می‌توانند تنها بخش encrypted یک document در غیر این صورت باز باشند، یا تنها بخش plaintext یک document encrypted. سه assignment filter را به‌عنوان fieldهای جداگانه برای stringها، streamها و embedded fileها ضبط کنید، و هیچ‌یک از این تله‌ها نمی‌توانند شما را بگیرند. یک boolean واحد ذخیره کنید و call اشتباه فقط مسئلهٔ زمان است

PDF Library for Delphi: جریان ممیزی بررسی StringFilterIdentity، StreamFilterIdentity، EncryptMetadata و فیلتر رمز فایل تعبیه‌شده پیش از آنکه یک PDF محافظت‌شده خوانده شود
چهار محور مستقل تعیین می‌کنند فایلی که رمزگذاری‌شده به نظر می‌رسد واقعاً مهر و موم شده است یا نه، و فروکاستن آن‌ها به یک بولین سرانجام فایلی را غلط دسته‌بندی می‌کند

برداشتن encryption، و انتخاب آن برای fileهای جدید

یک audit اغلب به تصمیمی برای strip کردن protection ختم می‌شود، و مکانیک آن‌جا مانع نیست. DecryptFile(InputFileName, OutputFileName, Password) یک copy decrypted بدون full load می‌نویسد، و Decrypt در سطح document-loaded همین کار را در memory می‌کند وقتی file از قبل باز است. هر دو به یک password معتبر نیاز دارند؛ هیچ‌کدام cryptography را bypass نمی‌کنند. دروازهٔ واقعی policy به‌جای code است، پس intake ruleهای خود را plainly بیان کنید که چه زمانی removal مجاز است و password classای که آن را authorize کرده ضبط کنید، چون step فنی خودش هیچ ردی به‌جا نمی‌گذارد

انتخاب برای output جدید تنگ‌تر از آن است که پنج مقدار Strength نشان می‌دهند. از Strength 4، AES-256 revision 6 استفاده کنید، مگر اینکه مجبور باشید fileها را در viewerهای قدیمی‌تر از Acrobat X باز کنید. Strength 2، AES-128، کف عمل‌گرایانه برای یک ناوگان viewer در حال پیری است که نمی‌تواند upgrade شود. optionهای RC4 در ۰ و ۱ آن‌جا هستند تا بتوانید archiveهای تاریخی را بخوانید و audit کنید، نه تا چیزی جدید با آن‌ها تولید کنید؛ درخواست کردن آن‌ها در یک design ۲۰۲۶ نشانه‌ای است که یک requirement بالادست stale است

encryption state مستقیماً به تصمیم‌های signing تغذیه می‌شود، چون یک workbench که documentها را validate و sign می‌کند به همان discipline read-back نیاز دارد که این audit به آن تکیه می‌کند. آن زمینه در مقالهٔ workbench compliance و signing پوشش داده شده. وقتی یک batch EncryptFile را روی هزاران document بزرگ اعمال می‌کند، راهنمای direct-access به PDFهای بزرگ نشان می‌دهد چگونه memory را flat نگه دارید در حالی که اجرا می‌شود. مرجع کامل API encryption در page محصول PDF Library for Delphi زندگی می‌کند