مقال تقني

تدقيق تشفير PDF وأذوناته في Delphi

علم الأذونات ليس آلية أمنية. فالبتّ الذي يقول "لا نسخ" يقيم داخل قاموس /Encrypt نفسه الذي يضم التشفير، وهذا يمنحه مظهر إنفاذ لا يملكه، وما إن تعامل الاثنين كشيء واحد حتى يبدأ تدقيقك بإنتاج إجابات خاطئة. والسؤال الوحيد الجدير بطرحه على ملف PDF ليس "هل هو مشفَّر". بل هو أكثر تحديدًا وأصعب: أي خوارزمية، وأي مراجعة لمعالج الأمان، وأي كلمتي المرور ضُبطت، وأي بتات أذونات مُدَّعاة، وأي أجزاء الملف يمسها التشفير فعليًا. فقد يكون الملف مشفَّرًا شكليًا ومفتوحًا عمليًا. وقد يرفض أن يُقرأ ومع ذلك يترك بياناته الوصفية نصًا صريحًا. وقد يقفل الطباعة في علم يحق لأي عارض تجاهله. وتدقيق ملف PDF يعني حسم كل ذلك على حدة، و PDF Library for Delphi، محرك PDF من losLab لـ Delphi و C++Builder، تكشف كلًّا منها عبر واجهة مسطّحة بمقابض صحيحة وطبقة أصناف مكتوبة بالأنواع معًا

ما الذي يسجّله قاموس /Encrypt فعليًا

يعرّف المعيار ISO 32000-1 §7.6 أمان المستند من خلال حفنة من مدخلات القاموس، وتعكسها PDF Library for Delphi واحدًا لواحد في السجل TPDFEncryption. يختار إصدار المرشّح V والمراجعة R عائلة الخوارزمية. ويحمل Length حجم المفتاح. وتقع بتات الأذونات في P، وسلاسل التحقق من كلمتي مرور المالك والمستخدم في O وU (مع إضافة OE وUE لـ AES-256)، ويرافقها علم EncryptMetadata، وتسمّي ثلاثة حقول أخرى مرشّحات التشفير المطبَّقة على السلاسل والدفوق والملفات المضمَّنة على التوالي

قيمة هذا السجل أنه لا يفسّر أي شيء نيابةً عنك. فهو يُعيد القاموس الخام ويدعك تستخلص الاستنتاجات، وهذا بالضبط ما يحتاجه التدقيق. وتظهر حالة النص الصريح داخل المشفَّر في StringFilterIdentity وStreamFilterIdentity: حين يكون أي منهما صحيحًا، تمرّ البيانات المقابلة عبر مرشّح Identity من دون مساس، مهما أبلغت حالة التشفير للمستند. وماسح يتوقف عند "قاموس /Encrypt موجود" سيسمّي مثل هذا الملف محميًا بينما سلاسله ودفوقه جالسة في العراء. وتحكم الفروق الدقيقة نفسها البيانات الوصفية. فحين يكون EncryptMetadata خاطئًا تبقى حزمة XMP مقروءة لأي مفهرس بينما لا يكون محتوى الصفحة كذلك، وهذا جدير بالمعرفة فور أن تعتمد قواعد التوجيه لديك على حقل عنوان أو مؤلف

مخطط PDF Library for Delphi لحقول قاموس /Encrypt في PDF مقابلةً لخصائص تدقيق TPDFEncryption بما فيها فخاخ مرشّح التشفير Identity
يقابل كل مدخل في /Encrypt حقلًا في TPDFEncryption، وتكشف أعلام مرشّح Identity أي السلاسل أو الدفوق أو البيانات الوصفية تبقى مقروءة بصرف النظر عن حالة التشفير

مسبار أمان قصير بالواجهة المسطّحة

في معظم خطوط الأنابيب، تجيب أربعة استدعاءات مسطّحة عن الأسئلة اليومية. تُعيد LoadFromFile القيمة 1 عند النجاح، وما إن يُفتح المستند حتى تُبلغ مفتّشات التشفير استنادًا إلى حالته المفكوكة:

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);     // مفكوك / مشفَّر / غير معروف
    Writeln('algorithm : ', PDF.EncryptionAlgorithm);  // RC4 مقابل عائلة AES
    Writeln('strength  : ', PDF.EncryptionStrength);   // فئة طول المفتاح
    Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
  finally
    PDF.Free;
  end;
end;

تهم CheckPassword أكثر مما يوحي توقيعها المكوّن من سطر واحد. فيعرّف PDF كلمتي مرور بقوة غير متكافئة. كلمة مرور المستخدم مطلوبة لفتح الملف أصلًا. وكلمة مرور المالك تمنح الحقوق الكاملة وتتجاوز كل بتّ أذونات. والبايتات على القرص متطابقة في الحالتين، لكن جلسة فُتحت بكلمة مرور المالك تستطيع فعل ما لا تستطيعه جلسة كلمة مرور المستخدم، لذا فإن تدقيقًا لا يسجّل أي بيانات اعتماد قُدِّمت يسجّل نصف الحقيقة. وتجعل طبقة الأصناف التمييز قابلًا للاستعلام. تُبلغ TPDFDocument.HasUserPassword وHasOwnerPassword عمّا يتطلبه الملف، بينما تُبلغ IsUserPassword وIsOwnerPassword عن أي كلمة مرور فتحت الجلسة الحالية فعليًا. سجّل تلك الحقيقة. ولا تسجّل قيم كلمات المرور نفسها أبدًا

سلّم Strength، حيث تعني "AES-256" شيئين

تأخذ الدالتان المسطّحتان Encrypt وEncryptFile قيمة Strength صحيحة بخمس قيم ذات معنى: 0 لـ RC4 بطول 40 بتًا، و1 لـ RC4 بطول 128 بتًا، و2 لـ AES بطول 128 بتًا قابل للقراءة من Acrobat 7، و3 لـ AES بطول 256 بتًا كما قُدِّم مع Acrobat 9، و4 لـ AES بطول 256 بتًا كما يتطلبه Acrobat X وما بعده

الجزء المثير أن 3 و4 كليهما موسومان AES-256 وليسا المخطط نفسه. تقابل Strength 3 مراجعة معالج الأمان 5، وهي تصميم مؤقت شحنه Acrobat 9 ولم يتبنّه ISO قط. وتقابل Strength 4 المراجعة 6، التي جرى تحصين دالة اشتقاق مفتاحها وتوحيدها قياسيًا في ISO 32000-2. ولمستند تنشئه اليوم لا يوجد سبب لاختيار 3 بدلًا من 4. أما للتدقيق فالفجوة حاسمة: سياسة تنص على "AES-256 وفق ISO 32000-2" لا يستوفيها إلا R6، وملف R5 يسمّي نفسه AES-256 يخفق في تلك السياسة بينما يجتاز فحص قوة ساذجًا. وتُبقي طبقة الأصناف الاثنين منفصلين بالاسم، esAES256Bit لـ R5 مقابل esAES256BitAcroX لـ R6، وتجيب الخاصية EncryptionAcroX عن سؤال المراجعة بقيمة منطقية واحدة

سلّم Strength لتشفير PDF من RC4 بطول 40 بتًا إلى AES-256 بالمراجعة 5 مقابل المراجعة 6 لعمليات التدقيق في Delphi
تُسمّى القيمتان 3 و4 كلتاهما AES-256، ومع ذلك لا تستوفي سياسة ISO 32000-2 إلا المراجعة 6، لذا يجب أن تسجّل عمليات التدقيق مراجعة المعالج لا التسمية وحدها

بتات الأذونات وشروطها الدقيقة المتعلقة بطول المفتاح

تحزم EncodePermissions ثمانية أعلام في العدد الصحيح الذي تتوقعه Encrypt وEncryptFile. تشكّل الطباعة والنسخ والتغيير وإضافة الملاحظات المجموعة الأساسية؛ وتشكّل تعبئة الحقول والنسخ لإمكانية الوصول والتجميع والطباعة بجودة كاملة المجموعة الموسّعة. والشرط الدقيق، الذي ينص عليه عرض التشفير التوضيحي الخاص بالمكتبة نفسها صراحةً، هو أن الأربعة الموسّعة لا تسري إلا عند قوة 128 بتًا فما فوق. ويخضع علم الطباعة بجودة كاملة للقاعدة نفسها: امسحه لفرض طباعة بدقة منخفضة وسيتجاهلك مستند بطول 40 بتًا، لأن ذلك التخفيض يتطلب أيضًا تشفيرًا بطول 128 بتًا أو أقوى. رمّز سياسة "طباعة بدقة منخفضة فقط" في ملف بطول 40 بتًا وسيطبع كل عارض بجودة كاملة على أي حال

والسؤال الأعمق هو من ينفذ أيًّا من هذه البتات، والجواب: لا أحد يمكنك الوثوق به. فالأذونات تعليمات للقارئات المتوافقة، لا قيود تشفيرية. ومفتاح فك التشفير متطابق سواء سُمح بالنسخ أو رُفض، لذا فإن مجموعة أذونات مقفلة لا تُبقي إلا العارضات الأمينة أمينة. وقارئ يختار تجاهل البتات لا يواجه أي عقبة تشفيرية على الإطلاق. فإن كان الالتزام منع الاستخراج لا تثبيطه، فالملف يحتاج إلى كلمة مرور مستخدم ويحتاج سير العمل إلى ضوابط على مستوى العملية حوله، وينبغي لتقرير التدقيق أن يسمّي أي النظامين يخضع له كل ملف فعليًا بدلًا من معاملة علم أذونات كقفل

ضبط السياسة وإثبات أنها ثبتت

لا يتطلب تطبيق التشفير على ملفات موجودة تحميلها في شجرة الكائنات. تعالج EncryptFile المدخل إلى المخرج في استدعاء واحد، وتعيد حلقة التدقيق فتح النتيجة لتأكيد ما استقر على القرص. ويتبع عرض التشفير التوضيحي المشحون الشكل نفسه من الكتابة ثم إعادة القراءة:

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,    // الطباعة مسموحة؛ النسخ/التغيير/الملاحظات مرفوضة
                            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;

وتحصل الفرق العاملة على طبقة المستند على العملية نفسها بمجموعات مكتوبة بالأنواع بدلًا من حزم البتات، وهو ما يجتاز مراجعة الكود بتحديق أقل بكثير:

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

وفي الحالتين، خطوة إعادة القراءة ليست طقسًا اختياريًا. فهي تلتقط أخطاء النشر التي تظهر خلاف ذلك بعد أشهر على جهاز عميل: بناء قديم للمكتبة يخفّض القوة المطلوبة بصمت، أو مسار إخراج لم يُكتب قط لأن المجلد للقراءة فقط، أو عدد صحيح للأذونات دخلت وسائطه بترتيب خاطئ. وتجتاز الثلاثة اختبار دخان محليًا وتفشل في الميدان، وإعادة فتح المخرج تحوّل كلًّا منها إلى استثناء تراه أثناء التشغيل الذي أنشأ الملف. وتُعيد GetEncryptionFingerprint قيمة مضغوطة يمكنك تخزينها مع سجل المهمة، بحيث تستطيع مقارنة لاحقة أن تحدد ما إذا كان مخرجان يتشاركان تكوين التشفير نفسه من دون إعادة فتح أي منهما

إيجابيات كاذبة في التدقيق تستحق كتابة كود لها

ثمة أنماط قليلة تدفع ماسحات الأمان بثبات إلى الاستنتاج الخاطئ، وكل منها ينبع من طيّ سؤال متعدد الأجزاء في إجابة بنعم أو لا. ومرشّح التشفير Identity هو المثال الأنظف. فقاموس /Encrypt موجود، والملف يُبلغ عن نفسه كمشفَّر، ومع ذلك تمرّ السلاسل والدفوق عبر مرشّح Identity من دون تغيير، فالمحتوى الحقيقي نص صريح. وقراءة StringFilterIdentity وStreamFilterIdentity قبل إعلان أي شيء محميًا هي الحل

وانقسام البيانات الوصفية أدق. فقد يختلف EncryptMetadata مع بقية المستند في الاتجاهين، تاركًا ملفًا مشفَّرًا بحزمة XMP مقروءة أو، في حالات أندر، العكس. و"الملف مشفَّر" لا تقول شيئًا عمّا إذا كانت بياناته الوصفية كذلك، وهذا مهم في اللحظة التي يمدّ فيها مفهرس أو قاعدة توجيه يده إلى العنوان. وتضيف الملفات المضمَّنة محورًا ثالثًا: يسمح PDF بمرشّح تشفير مخصص للمرفقات وحدها، لذا قد تكون المرفقات الجزء المشفَّر الوحيد في مستند مفتوح عدا ذلك، أو الجزء الصريح الوحيد في مستند مشفَّر. التقط تعيينات المرشّحات الثلاثة كحقول منفصلة للسلاسل والدفوق والملفات المضمَّنة، فلا يستطيع أي من هذه الفخاخ الإيقاع بك. وخزّن قيمة منطقية واحدة يكون الحكم الخاطئ مسألة وقت فحسب

PDF Library for Delphi: تدفق تدقيق يفحص StringFilterIdentity و StreamFilterIdentity و EncryptMetadata ومرشّح تشفير الملفات المضمَّنة قبل تسمية ملف PDF محميًا
أربعة محاور مستقلة تقرر ما إذا كان ملف يبدو مشفَّرًا مختومًا حقًا، وطيّها في قيمة منطقية واحدة يسيء تصنيف ملف ما في نهاية المطاف

إزالة التشفير، واختياره للملفات الجديدة

كثيرًا ما ينتهي التدقيق بقرار نزع الحماية، والآليات ليست العقبة هناك. تكتب DecryptFile(InputFileName, OutputFileName, Password) نسخة مفكوكة من دون تحميل كامل، وتفعل Decrypt الخاصة بالمستند المحمَّل الشيء نفسه في الذاكرة ما إن يكون الملف مفتوحًا بالفعل. وكلتاهما تتطلب كلمة مرور صالحة؛ ولا تتجاوز أي منهما التشفير. والبوابة الحقيقية هي السياسة لا الكود، لذا اجعل قواعد الاستلام لديك تنص بوضوح متى يُسمح بالإزالة وسجّل فئة كلمة المرور التي أذنت بها، لأن الخطوة التقنية نفسها لا تترك أثرًا

والخيار للمخرجات الجديدة أضيق مما توحي به قيم Strength الخمس. استخدم Strength 4، أي AES-256 بالمراجعة 6، ما لم تضطر إلى فتح الملفات في عارضات أقدم من Acrobat X. و Strength 2، أي AES-128، هي الحد الأدنى العملي لأسطول عارضات متقادم لا يمكن ترقيته. أما خيارا RC4 عند 0 و1 فموجودان لتتمكن من قراءة الأرشيفات التاريخية وتدقيقها، لا لتنتج بهما أي شيء جديد؛ واللجوء إليهما في تصميم من عام 2026 علامة على أن متطلبًا في المنبع قد تقادم

وتصب حالة التشفير مباشرة في قرارات التوقيع، إذ تحتاج منضدة عمل تتحقق من المستندات وتوقّعها إلى انضباط إعادة القراءة نفسه الذي يعتمد عليه هذا التدقيق. وتلك الأرض مغطاة في مقال منضدة عمل الامتثال والتوقيع. وحين تطبّق دفعة EncryptFile على آلاف المستندات الكبيرة، يُظهر دليل الوصول المباشر إلى ملفات PDF الكبيرة كيف تُبقي الذاكرة ثابتة أثناء تشغيلها. ويقيم مرجع واجهة التشفير الكامل على صفحة منتج PDF Library for Delphi