تخطَّ إلى المحتوى

تشفير ملف PDF وتقييد الأذونات

تشرح هذه الوصفة تشفير مستند باستخدام معالج الأمان القياسي ⁨Advanced Encryption Standard⁩ (⁨AES⁩)-256. حدّد كلمة مرور للمستخدم (مطلوبة للفتح)، وكلمة مرور للمالك (وصول كامل)، وقناع بتات للأذونات يقيّد العمليات. تعامل مع تلك الأذونات بوصفها معتمِدة على تعاون القارئ: فالتشفير يمنح السرية لا السلامة، ولا تحترم بتات الأذونات إلا البرمجيات المتعاونة. تتبع الوصفة examples/22-protection.php.

حد الثقة (احمل هذا مع كل ادعاء متعلق بالأذونات). تشفير ⁨PDF⁩ يحمي سرية المحتوى من الأطراف التي لا تملك كلمة المرور (⁨ISO 32000-2⁩ §7.6). وهو لا يحمي السلامة: فهو لا يكتشف ولا يمنع التعديل. مُدخَل الأذونات P هو مجموعة أعلام بطول 32 بت غير مُوقَّعة تطلب من القارئات المطابِقة احترام القيود؛ وهي ليست تحكمًا في الوصول. أي أداة غير مطابِقة، أو أي أداة تُستخدم مع كلمة مرور المالك، يمكنها تنفيذ كل عملية “مرفوضة”. لا تصِف ملف ⁨PDF⁩ مشفَّرًا بأنه “آمن”، أو “مقاوم للعبث”، أو “محمي من النسخ”.

Terminal window
composer require nextpdf/core:^3

فعّل امتداد ⁨PHP⁩ المسمّى openssl. يستخدمه مُشفّر ⁨AES-256⁩ في التشفير واشتقاق المفتاح.

يحدّد رمزا قاموس التشفير V/R معالج الأمان القياسي (⁨ISO 32000-2⁩ §7.6). يُنفّذ Aes256Encryptor الخاص بـ⁨NextPDF⁩ مرشّح التشفير ⁨AESV3⁩ عند المراجعة 6 لمعالج الأمان (V=5/R=6). يستخدم مفتاح تشفير ملف عشوائيًّا بطول 256 بت، واشتقاق مفتاح بتجزئة تكرارية مُملَّحة (الخوارزمية 2.⁨B⁩)، وتشفيرًا لكل كائن بنمط ⁨AES-256-CBC⁩ مع متجه تهيئة عشوائي. يُعد تسلسل كتل الشيفرة ⁨Cipher Block Chaining⁩ (⁨CBC⁩) نمط سرية (⁨NIST SP 800-38A⁩)، ويجب أن تكون متجهات التهيئة الخاصة به غير قابلة للتنبؤ.

متجه التهيئة جديد لكل كائن ولكل تشغيل، لذلك تختلف البايتات الخام من تشغيل إلى آخر. لذلك يكون ملف تعريف قابلية إعادة الإنتاج هو structural. قبل أن تقارن مجموعة الاختبار بين تشغيلين، تُطبّع متجه تهيئة التشفير، وترتيب الكائنات، و/ID الخاص بالمذيّل. هذا الملف أكثر صرامة من ملف وصفة لا تستخدم التشفير.

يُضبط قناع بتات الأذونات في المُدخَل P. يمنح البت 3 الطباعة، ويمنح البت 6 ⁨annotation/form-fill.⁩ والقيمة هي الكمية الموثَّقة بطول 32 بت غير مُوقَّع.

سطح واجهة برمجة التطبيقات ⁨API⁩

قسم بعنوان «سطح واجهة برمجة التطبيقات ⁨API⁩»

NextPDF\Core\Concerns\HasSecurity (مدمَج في Document):

  • setEncryption(#[SensitiveParameter] string $userPassword, #[SensitiveParameter] string $ownerPassword = '', int $permissions = -1): static — يُهيّئ تشفير معالج الأمان القياسي ⁨AES-256.⁩ يمنح permissions = -1 جميع الأذونات. عندما تكون ownerPassword فارغة، تُعاد كلمة مرور المستخدم استخدامًا ككلمة مرور للمالك. استدعِها قبل addPage().
  • getEncryptor(): ?Aes256Encryptor — المُشفّر المُهيَّأ، أو null.
  • useAesGcm(?bool $enabled = true): static — يفعّل الاشتراك في ⁨ISO/TS 32003 AES-256-GCM⁩؛ ويرمي استثناءً إذا لم يوفّر ⁨OpenSSL/libsodium⁩ المضيف البدائيات التشفيرية.

كلتا معلَمتي كلمتي المرور موسومتان بـ#[SensitiveParameter]، لذا تحجبهما ⁨PHP⁩ من تتبّعات المكدّس.

بتات الأذونات (المُدخَل P، البتات الدنيا 3–6 الشائعة الاستخدام):

البتالقيمةالعملية
34طباعة المستند
48تعديل محتويات المستند
516نسخ / استخراج النص والرسوم
632إضافة التعليقات التوضيحية أو تعديلها وتعبئة حقول النماذج
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Confidential Memo');
// Grant printing only (bit 3 = 4). MUST run before addPage().
$doc->setEncryption(
userPassword: 'open-me',
ownerPassword: 'owner-secret',
permissions: 4,
);
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'Encrypted with AES-256; printing allowed only.', newLine: true);
$doc->save(__DIR__ . '/confidential.pdf');
echo "Wrote confidential.pdf\n";

نموذج شيفرة — بيئة الإنتاج

قسم بعنوان «نموذج شيفرة — بيئة الإنتاج»

يعكس المثال الكامل أدناه examples/22-protection.php ويكتب إلى NEXTPDF_COOKBOOK_OUTPUT من أجل مجموعة الاختبار.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$userPassword = 'demo';
$ownerPassword = 'admin';
// Grant ONLY printing (bit 3 = 4); deny copy/modify/annotate.
$permissions = 4;
$doc = Document::createStandalone();
$doc->setTitle('Encrypted Document — Restricted Permissions');
$doc->setAuthor('NextPDF Example');
// setEncryption() MUST be called before addPage().
$doc->setEncryption(
userPassword: $userPassword,
ownerPassword: $ownerPassword,
permissions: $permissions,
);
$doc->addPage();
$doc->setFont('helvetica', 'B', 20);
$doc->cell(0, 14, 'Encrypted PDF Document', newLine: true);
$doc->ln(8);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, 'This document is protected with AES-256 encryption '
. '(standard security handler, revision 6). The user password is required '
. 'to open it; the owner password grants full access. The permission '
. 'bits below are honoured by conforming readers only.');
$doc->ln(5);
$permissionTable = [
['Bit 3 (4)', 'Printing', 'ALLOWED'],
['Bit 4 (8)', 'Content modification', 'DENIED'],
['Bit 5 (16)', 'Text copying / extraction', 'DENIED'],
['Bit 6 (32)', 'Annotations / form fields', 'DENIED'],
];
$doc->setFont('helvetica', 'B', 10);
$doc->cell(30, 7, 'Flag');
$doc->cell(60, 7, 'Operation');
$doc->cell(0, 7, 'Status', newLine: true);
foreach ($permissionTable as [$bit, $operation, $status]) {
$doc->setFont('courier', '', 9);
$doc->cell(30, 7, $bit);
$doc->setFont('helvetica', '', 10);
$doc->cell(60, 7, $operation);
$doc->setFont('helvetica', 'B', 10);
$doc->cell(0, 7, $status, newLine: true);
}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/encrypted.pdf');
echo "Wrote encrypted PDF (AES-256, printing only)\n";

الخرج المتوقع:

Wrote encrypted PDF (AES-256, printing only)

عند فتح الملف، يطلب القارئ كلمة مرور. تفتح كلمة مرور المستخدم الملف بمجموعة الأذونات المقيَّدة. وتفتحه كلمة مرور المالك بوصول كامل.

  • ترتيب الاستدعاء. استدعاء setEncryption() بعد addPage() لا يُشفّر المحتوى السابق بأثر رجعي. هيّئ التشفير أولًا؛ فالمحرك يُشفّر جسم كل كائن أثناء كتابته.
  • القيمة الافتراضية لكلمة مرور المالك. تجعل كلمة مرور المالك الفارغة المحرك يعيد استخدام كلمة مرور المستخدم ككلمة مرور للمالك. وبذلك لا يبقى عمليًّا دور منفصل ذو امتياز. عيّن كلمتي مرور متمايزتين عندما يلزم فصل الدورين.
  • دلالات الأذونات إرشادية. لا تحترم البتات إلا القارئات المطابِقة. وهي لا تُفرَض تشفيريًّا: فأي أداة غير مطابِقة، أو أي أداة تُستخدم مع كلمة مرور المالك، يمكنها تنفيذ العمليات المقيَّدة. تعامل مع الأذونات بوصفها إشارة سياسة موجَّهة إلى البرمجيات المتعاونة، لا بوصفها تحكمًا في الوصول يصمد أمام طرف مُصِرّ.
  • لا ضمان للسلامة. التشفير يوفّر السرية لا السلامة. لا يستطيع مهاجم لا يملك كلمة المرور قراءة المحتوى، لكن التنسيق نفسه لا يكتشف العبث. تتطلب حماية السلامة آلية منفصلة، مثل توقيع رقمي أو ⁨ISO/TS 32004 document MAC.⁩
  • تعارض مع ⁨PDF/A.⁩ يحظر ⁨PDF/A⁩ مفتاح المذيّل Encrypt. إن استدعاء setEncryption() على مستند ⁨PDF/A⁩، بأي ترتيب، يرمي استثناء عدم توافق.
  • الاشتراك في ⁨AES-256-GCM.⁩ يختار useAesGcm() التشفير المُجمَّع ⁨ISO/TS 32003 GCM⁩ عندما يوفّره ⁨OpenSSL⁩ أو ⁨libsodium⁩ المضيف. وإلا، فإنه يرمي InvalidConfigException. وهو غير متوافق مع ⁨PDF/A⁩ للسبب نفسه.
  • تشفير المفتاح العام غير موصول بعد. يثبّت setPublicKeyEncryption() سطح واجهة البرمجة ⁨API⁩، لكن save() يرمي استثناءً إلى أن يُنجَز توصيل الكاتب (عيب معروف). لا تستخدمه في بيئة الإنتاج على ⁨Core.⁩

يشغّل اشتقاق المفتاح التجزئة التكرارية في الخوارزمية 2.⁨B⁩ مرة واحدة لكل مستند. تشفير ⁨AES-256-CBC⁩ لكل كائن خطّي في حجم جسم الكائن. بالنسبة إلى المستندات النموذجية، تظل التكلفة ضمن ميزانية 1500 ⁨ms⁩ / 64 ⁨MB⁩ بهامش مريح. تتحمّل المستندات الكبيرة جدًّا تكلفة مرتبطة بمعدل إنتاجية ⁨AES⁩ لكل كائن. نمط ⁨Galois/Counter Mode⁩ (⁨GCM⁩) مع ⁨AES-NI⁩ أسرع على المضيفات القادرة.

  • السرية فقط. تكرارًا لحد الثقة: يُبقي التشفير المحتوى بعيدًا عن الأطراف التي لا تملك كلمة المرور. وهو لا يُثبِت أن الملف لم يُعدَّل، وبتات الأذونات معتمِدة على تعاون القارئ.
  • قوة كلمة المرور مسؤوليتك. المعالج ليس أقوى من كلمات المرور. بمجرد أن يحصل أحدهم على الملف، يمكن كسر كلمة مرور المستخدم الضعيفة بالقوة الغاشمة دون اتصال؛ ولا يستطيع التنسيق تقييد معدّل المحاولات.
  • كلمة مرور المالك مفتاح أساسي. أي شخص يملك كلمة مرور المالك يتجاوز كل قيد. تعامل معها كسر اعتماد جذري؛ لا تضمّنها مع المستند أبدًا ولا تُسجّلها.
  • #[SensitiveParameter] دفاع متعمّق. يحجب كلمات المرور من تتبّعات مكدّس ⁨PHP⁩، لكن لا يزال عليك إبقاؤها خارج سجلّاتك ورسائل الاستثناءات وتقارير الأعطال الخاصة بك.

إقامة البيانات وتدابير التخفيف الخاصة بمعلومات التعريف الشخصية ⁨PII⁩

قسم بعنوان «إقامة البيانات وتدابير التخفيف الخاصة بمعلومات التعريف الشخصية ⁨PII⁩»

تنفّذ المكتبة التشفير داخل العملية. وهي لا تنقل المستند أو كلمات المرور إلى أي مكان. لا يكتب المحرك أي كلمة مرور أو مفتاح أو بايت من المستند إلى القرص باستثناء الخرج المشفَّر الذي تحفظه. إن مكان إقامة ملف الخرج، وكيفية حفظ كلمات المرور، شأنان من شؤون النشر يملكهما المُكامِل. لا تقدّم المكتبة أي ضمان لإقامة البيانات. إذا احتوى المستند النصي الصريح على بيانات شخصية، فإن تلك البيانات ليست أكثر حمايةً من أضعف كلمة مرور ومن مدى التزام القارئ المتعاون المذكور أعلاه. التشفير ليس بديلًا عن تقليل معلومات التعريف الشخصية (⁨PII⁩) التي تضعها في المستند.

القياس عن بُعد الآمن وتنظيف السجلّات

قسم بعنوان «القياس عن بُعد الآمن وتنظيف السجلّات»

يُصدِر التشفير حدث EncryptionAppliedEvent يحمل اسم الخوارزمية فقط (AES-256) وثلاث قيم منطقية تلخّص ما إذا كانت ⁨print/copy/modify⁩ مسموحة — لا تُضاف إلى الحدث أبدًا أي كلمة مرور أو مفتاح أو مِلح أو متجه تهيئة (src/Event/Security/EncryptionAppliedEvent.php). يُمرّر مسار ⁨OpenTelemetry⁩ سمات النطاق عبر مُنظِّف بقائمة سماح (src/Telemetry/AttributeSanitizer.php) يرفض كلمات المرور ومسارات الملفات دون قيد أو شرط؛ ولا تبقى إلا المفاتيح المُدرَجة في قائمة السماح ذات القيم العددية. لا تُضِف أي مادة متعلقة بكلمة مرور أو مفتاح إلى النطاقات أو السجلّات أو رسائل الاستثناءات في شيفرة التكامل الخاصة بك. تحمي وسوم #[SensitiveParameter] تتبّعات المكدّس، لكنها لا تحمي السلاسل النصية التي تبنيها بنفسك.

ضمن النطاق: خصم يحصل على الملف المشفَّر دون كلمات المرور. لا يستطيع قراءة المحتوى، بحسب قوة كلمة المرور، والملف لا يُسرّب نصًّا صريحًا. خارج النطاق: خصم يملك كلمة مرور المستخدم أو المالك؛ وقارئ غير مطابِق يتجاهل بتات الأذونات؛ وكسر بالقوة الغاشمة دون اتصال لكلمة مرور ضعيفة؛ واكتشاف العبث (يوفّر التشفير السرية لا السلامة)؛ والقنوات الجانبية في بنية ⁨OpenSSL⁩ المضيفة؛ وإدارة المفاتيح، التي تقع بالكامل على مسؤولية المُكامِل. توثيق هذه التهديدات لا يؤكّد غياب الثغرات.

توفّر بنية ⁨OpenSSL⁩ المضيفة البدائيات التشفيرية، لذا فإن وضع ⁨FIPS⁩ خاصية للمضيف، وليس إعدادًا للمكتبة. يُرجِع CryptoCapabilities::detectFipsMode() قيمة FipsModeDetection ثلاثية الحالات (src/Security/FipsModeDetection.php): FIPS_ACTIVE أو FIPS_ABSENT أو INDETERMINATE. لا يُتيح امتداد ⁨openssl⁩ في ⁨PHP⁩ أي ربط بنموذج موفِّر ⁨OpenSSL 3⁩، لذا فإن الفحص يُنفَّذ بأفضل جهد ممكن. تُعامَل INDETERMINATE على أنها “⁨FIPS⁩ غير مُثبَت” (فشل آمن)، ويمكن تمييزها في قياس عن بُعد يستطيع المشغّل اتخاذ إجراء بناءً عليه. لا يدّعي ⁨NextPDF⁩ اعتماد ⁨FIPS 140⁩؛ والتشغيل على ⁨OpenSSL⁩ معتمَد وفق ⁨FIPS⁩ مسؤولية المشغّل، ونتيجة الكشف إرشادية.

العبارةالمواصفةالبند⁨reference_id⁩
يختار رمز قاموس التشفير V خوارزمية التشفير.⁨ISO 32000-2⁩§7.6
يمثّل المُدخَل CFM طريقة مرشّح التشفير ⁨AESV3.⁩⁨ISO 32000-2⁩§7.6
المُدخَل P كمية أذونات وصول بطول 32 بت غير مُوقَّع.⁨ISO 32000-2⁩§7.6
يتحكّم بت الأذونات 3 في الطباعة.⁨ISO 32000-2⁩§7.6
يتحكّم بت الأذونات 6 في التعليق التوضيحي / تعبئة النماذج.⁨ISO 32000-2⁩§7.6
يحمي التشفير المحتويات من الوصول غير المصرَّح به (السرية).⁨ISO 32000-2⁩§7.6
يستخدم اشتقاق المفتاح في المراجعة 6 تجزئة تكرارية مُملَّحة (الخوارزمية 2.⁨B⁩).⁨ISO 32000-2⁩§7.6
يُعد ⁨CBC⁩ نمط سرية (لا نمط سلامة).⁨NIST SP 800-38A⁩§6.2
يجب أن تكون متجهات تهيئة ⁨CBC⁩ غير قابلة للتنبؤ.⁨NIST SP 800-38A⁩الملحق ⁨C⁩

يُنفّذ ⁨NextPDF⁩ البنود المذكورة. وهو لا يؤكّد مطابقة شاملة لـ⁨ISO 32000-2⁩، ولا اعتماد ⁨FIPS 140⁩، ولا أي ضمان قانوني أو تعاقدي للسرية. إن “دعم معالج الأمان القياسي” ليس شهادة أمان في نشرك. يعتمد ذلك على حفظ كلمات المرور وسياسة المُتحقِّق خارج المكتبة.