เข้ารหัสลับ PDF และจำกัดสิทธิ์การใช้งาน
ภาพรวมโดยสังเขป
หัวข้อที่มีชื่อว่า “ภาพรวมโดยสังเขป”สูตรนี้เข้ารหัสลับเอกสารด้วย standard security handler ของ Advanced Encryption Standard (AES)-256 คุณกำหนดรหัสผ่านผู้ใช้ (จำเป็นสำหรับการเปิดเอกสาร) รหัสผ่านเจ้าของ (ให้การเข้าถึงแบบเต็มรูปแบบ) และ permission bitmask ที่จำกัดการดำเนินการ โปรดถือว่าสิทธิ์เหล่านี้เป็นแบบ reader-cooperative: การเข้ารหัสลับให้ confidentiality ไม่ใช่ integrity และมีเพียงซอฟต์แวร์ที่ให้ความร่วมมือเท่านั้นที่เคารพ permission bits สูตรนี้อ้างอิงตาม examples/22-protection.php
ขอบเขตความเชื่อถือ (ควรระลึกถึงทุกครั้งที่กล่าวถึงสิทธิ์) การเข้ารหัสลับ PDF ปกป้อง confidentiality ของเนื้อหาจากบุคคลที่ไม่มีรหัสผ่าน (ISO 32000-2 §7.6) ไม่ได้ ปกป้อง integrity: ไม่ตรวจจับหรือป้องกัน การแก้ไข รายการสิทธิ์
Pเป็นชุดแฟล็กแบบ unsigned 32-bit ที่ขอให้โปรแกรมอ่านที่สอดคล้องตามมาตรฐานเคารพข้อจำกัด แต่ไม่ใช่ access control เครื่องมือที่ไม่สอดคล้องตามมาตรฐานหรือเครื่องมือใดก็ตามที่ใช้กับรหัสผ่านเจ้าของ สามารถดำเนินการ “ที่ถูกปฏิเสธ” ได้ทุกอย่าง อย่าอธิบายว่า PDF ที่เข้ารหัสลับแล้วเป็น “secure” “tamper-proof” หรือ “copy-protected”
การติดตั้ง
หัวข้อที่มีชื่อว่า “การติดตั้ง”composer require nextpdf/core:^3เปิดใช้งานส่วนขยาย PHP openssl ตัวเข้ารหัสลับ AES-256 ใช้ส่วนขยายนี้สำหรับ cipher และการสร้างคีย์
ภาพรวมเชิงแนวคิด
หัวข้อที่มีชื่อว่า “ภาพรวมเชิงแนวคิด”โค้ด V/R ใน encryption dictionary เป็นตัวเลือก standard security handler (ISO 32000-2 §7.6) NextPDF ใช้ Aes256Encryptor เพื่อนำ AESV3 crypt filter มาใช้ที่ security handler revision 6 (V=5/R=6) โดยใช้ file-encryption key แบบสุ่มขนาด 256-bit การสร้างคีย์ด้วย salted iterative-hash (Algorithm 2.B) และการเข้ารหัสลับแบบ AES-256-CBC ต่อออบเจ็กต์พร้อม initialization vector แบบสุ่ม Cipher Block Chaining (CBC) เป็นโหมด confidentiality (NIST SP 800-38A) initialization vector ของโหมดนี้ต้องคาดเดาไม่ได้
initialization vector เป็นค่าใหม่สำหรับแต่ละออบเจ็กต์และแต่ละครั้งที่รัน ดังนั้นไบต์ดิบจึงแตกต่างกันทุกครั้งที่รัน โปรไฟล์ความสามารถในการทำซ้ำจึงเป็น structural ก่อนเปรียบเทียบการรันสองครั้ง harness จะทำให้ encryption IV ลำดับออบเจ็กต์ และ /ID ใน trailer อยู่ในรูปแบบ canonical โปรไฟล์นี้เข้มงวดกว่าโปรไฟล์ของสูตรที่ไม่มีการเข้ารหัสลับ
permission bitmask เป็นตัวกำหนดรายการ P bit 3 ให้สิทธิ์การพิมพ์ และ bit 6 ให้สิทธิ์ annotation/form-fill ค่าดังกล่าวเป็นปริมาณ unsigned-32-bit ตามที่บันทึกไว้
พื้นผิว API
หัวข้อที่มีชื่อว่า “พื้นผิว API”NextPDF\Core\Concerns\HasSecurity (ผสานเข้ากับ Document):
setEncryption(#[SensitiveParameter] string $userPassword, #[SensitiveParameter] string $ownerPassword = '', int $permissions = -1): static— กำหนดค่าการเข้ารหัสลับแบบ standard-handler ด้วย AES-256permissions = -1ให้สิทธิ์ทั้งหมด เมื่อownerPasswordว่างเปล่า ระบบจะนำรหัสผ่านผู้ใช้มาใช้ซ้ำเป็นรหัสผ่านเจ้าของ เรียกก่อนaddPage().getEncryptor(): ?Aes256Encryptor— ตัวเข้ารหัสลับที่กำหนดค่าไว้ หรือnulluseAesGcm(?bool $enabled = true): static— เลือกใช้ AES-256-GCM ตาม ISO/TS 32003 จะ throw หาก OpenSSL/libsodium บนโฮสต์ไม่มี cipher นี้
พารามิเตอร์รหัสผ่านทั้งสองถูกทำเครื่องหมายไว้ด้วย #[SensitiveParameter] ดังนั้น PHP จึงปกปิดค่าเหล่านี้จาก stack traces
Permission bits (รายการ P โดย bit ต่ำ 3–6 ที่ใช้กันทั่วไป):
| บิต (Bit) | ค่า | การดำเนินการ |
|---|---|---|
| 3 | 4 | พิมพ์เอกสาร |
| 4 | 8 | แก้ไขเนื้อหาเอกสาร |
| 5 | 16 | คัดลอก / สกัดข้อความและกราฟิก |
| 6 | 32 | เพิ่มหรือแก้ไข annotation และกรอกฟิลด์ฟอร์ม |
ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว”<?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 สำหรับ harness
<?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()จะไม่เข้ารหัสลับเนื้อหาก่อนหน้าย้อนหลัง จงกำหนดค่าการเข้ารหัสลับก่อน เอนจินจะเข้ารหัสลับเนื้อหาของแต่ละออบเจ็กต์ขณะที่เขียน - ค่าเริ่มต้นของรหัสผ่านเจ้าของ รหัสผ่านเจ้าของที่ว่างเปล่าทำให้เอนจินนำรหัสผ่านผู้ใช้มาใช้ซ้ำเป็นรหัสผ่านเจ้าของ ซึ่งทำให้แทบไม่มีบทบาทที่มีสิทธิพิเศษ จงกำหนดรหัสผ่านที่แตกต่างกันเมื่อทั้งสองบทบาทต้องแยกจากกัน
- ความหมายของสิทธิ์เป็นเพียงคำแนะนำ มีเพียงโปรแกรมอ่านที่สอดคล้องตามมาตรฐานเท่านั้นที่เคารพ bits สิทธิ์เหล่านี้ไม่ได้ถูกบังคับใช้ด้วยการเข้ารหัสลับ: เครื่องมือที่ไม่สอดคล้องตามมาตรฐานหรือเครื่องมือใดก็ตามที่ใช้กับรหัสผ่านเจ้าของสามารถดำเนินการที่ถูกจำกัดได้ โปรดถือว่าสิทธิ์เป็นสัญญาณเชิงนโยบายต่อซอฟต์แวร์ที่ให้ความร่วมมือ ไม่ใช่ access control ที่ทนทานต่อผู้ที่มุ่งมั่นจะเข้าถึง
- ไม่มีการรับประกัน integrity การเข้ารหัสลับคือ confidentiality ไม่ใช่ integrity ผู้โจมตีที่ไม่มีรหัสผ่านไม่สามารถอ่านเนื้อหาได้ แต่ตัวรูปแบบเองไม่ตรวจจับการดัดแปลง การปกป้อง integrity ต้องใช้กลไกแยกต่างหาก เช่น ลายเซ็นดิจิทัลหรือ document MAC ตาม ISO/TS 32004
- ความขัดแย้งกับ PDF/A PDF/A ห้ามใช้คีย์
Encryptใน trailer การเรียกsetEncryption()บนเอกสาร PDF/A ไม่ว่าด้วยลำดับใดจะ throw ข้อยกเว้นความไม่เข้ากัน - การเลือกใช้ AES-256-GCM
useAesGcm()เลือกการเข้ารหัสลับจำนวนมากแบบ GCM ตาม ISO/TS 32003 เมื่อ OpenSSL หรือ libsodium บนโฮสต์มีให้ มิฉะนั้นจะ throwInvalidConfigExceptionการเข้ารหัสลับนี้เข้ากันไม่ได้กับ PDF/A ด้วยเหตุผลเดียวกัน - การเข้ารหัสลับด้วย public-key ยังไม่ได้เชื่อมต่อ
setPublicKeyEncryption()ตรึงพื้นผิว API ไว้ แต่save()จะ throw จนกว่าการเชื่อมต่อ writer จะพร้อม (ข้อบกพร่องที่ทราบแล้ว) อย่าใช้ใน production บน Core
ประสิทธิภาพ
หัวข้อที่มีชื่อว่า “ประสิทธิภาพ”การสร้างคีย์รัน iterated hash ของ Algorithm 2.B หนึ่งครั้งต่อเอกสาร AES-256-CBC ต่อออบเจ็กต์มีต้นทุนเชิงเส้นตามขนาดเนื้อหาของออบเจ็กต์ สำหรับเอกสารทั่วไป ต้นทุนยังคงอยู่ในงบประมาณ 1500 ms / 64 MB ได้อย่างสบาย เอกสารขนาดใหญ่มากจะมีต้นทุน AES throughput ต่อออบเจ็กต์ Galois/Counter Mode (GCM) ร่วมกับ AES-NI เร็วกว่าบนโฮสต์ที่รองรับ
หมายเหตุด้านความปลอดภัย
หัวข้อที่มีชื่อว่า “หมายเหตุด้านความปลอดภัย”- confidentiality เท่านั้น ขอย้ำขอบเขตความเชื่อถือ: การเข้ารหัสลับปกป้องเนื้อหาจากบุคคลที่ไม่มีรหัสผ่าน การเข้ารหัสลับไม่ได้พิสูจน์ว่าไฟล์ไม่ถูกแก้ไข และ permission bits เป็นแบบ reader-cooperative
- ความแข็งแกร่งของรหัสผ่านเป็นความรับผิดชอบของคุณ handler แข็งแกร่งได้เพียงเท่ากับรหัสผ่าน เมื่อมีผู้ได้ไฟล์ไป รหัสผ่านผู้ใช้ที่อ่อนแอสามารถถูก brute-force แบบออฟไลน์ได้ และตัวรูปแบบไม่สามารถจำกัดอัตราการพยายามได้
- รหัสผ่านเจ้าของเป็นคีย์หลัก ผู้ที่มีรหัสผ่านเจ้าของจะข้ามทุกข้อจำกัด จงปฏิบัติต่อรหัสผ่านเจ้าของเหมือน root credential อย่าส่งไปพร้อมกับเอกสารหรือบันทึกลงล็อก
#[SensitiveParameter]เป็นการป้องกันเชิงลึก เครื่องหมายนี้ปกปิดรหัสผ่านจาก PHP stack traces แต่คุณยังต้องไม่ให้รหัสผ่านปรากฏในล็อก ข้อความข้อยกเว้น และรายงานข้อขัดข้องของคุณเอง
ถิ่นที่อยู่ของข้อมูลและมาตรการลด PII
หัวข้อที่มีชื่อว่า “ถิ่นที่อยู่ของข้อมูลและมาตรการลด PII”ไลบรารีดำเนินการเข้ารหัสลับภายในกระบวนการ ไลบรารีไม่ส่งเอกสารหรือรหัสผ่านไปที่ใด เอนจินไม่เขียนรหัสผ่าน คีย์ หรือไบต์ของเอกสารลงดิสก์ ยกเว้นผลลัพธ์ที่เข้ารหัสลับแล้วซึ่งคุณบันทึก ตำแหน่งของไฟล์ผลลัพธ์และวิธีเก็บรหัสผ่านเป็นข้อพิจารณาด้าน deployment ที่ผู้รวมระบบเป็นเจ้าของ ไลบรารีไม่รับประกันถิ่นที่อยู่ของข้อมูล หากเอกสาร plaintext มีข้อมูลส่วนบุคคล ข้อมูลนั้นได้รับการปกป้องเพียงเท่ากับรหัสผ่านที่อ่อนแอที่สุดและข้อควรระวังเรื่อง cooperating-reader ข้างต้น การเข้ารหัสลับไม่ใช่สิ่งทดแทนการลดข้อมูลที่ระบุตัวบุคคลได้ (PII) ที่คุณใส่ไว้ในเอกสาร
Telemetry ที่ปลอดภัยและการขัดล้างล็อก
หัวข้อที่มีชื่อว่า “Telemetry ที่ปลอดภัยและการขัดล้างล็อก”การเข้ารหัสลับปล่อย EncryptionAppliedEvent ที่บรรจุเพียงชื่ออัลกอริทึม (AES-256) และบูลีนสามตัวที่สรุปว่าอนุญาตให้ print/copy/modify หรือไม่ — ไม่มีการวางรหัสผ่าน คีย์ salt หรือ IV ลงบน event เลย (src/Event/Security/EncryptionAppliedEvent.php) เส้นทาง OpenTelemetry ส่ง span attributes ผ่าน allowlist sanitizer (src/Telemetry/AttributeSanitizer.php) ที่ปฏิเสธรหัสผ่านและพาธของไฟล์โดยไม่มีเงื่อนไข มีเพียงคีย์ใน allowlist ที่มีค่าแบบ scalar เท่านั้นที่ผ่าน อย่าเพิ่มรหัสผ่านหรือสาระสำคัญของคีย์ลงใน span ล็อก หรือข้อความข้อยกเว้นในโค้ดการรวมระบบของคุณเอง เครื่องหมาย #[SensitiveParameter] ปกป้อง stack traces แต่ไม่ปกป้องสตริงที่คุณสร้างขึ้นเอง
แบบจำลองภัยคุกคาม
หัวข้อที่มีชื่อว่า “แบบจำลองภัยคุกคาม”อยู่ในขอบเขต: ผู้ไม่ประสงค์ดีที่ได้ไฟล์ที่เข้ารหัสลับแล้วแต่ไม่มีรหัสผ่าน ผู้ไม่ประสงค์ดีนั้นไม่สามารถอ่านเนื้อหาได้ ทั้งนี้ขึ้นอยู่กับความแข็งแกร่งของรหัสผ่านและการไม่รั่วไหลของ plaintext นอกขอบเขต: ผู้ไม่ประสงค์ดีที่มีรหัสผ่านผู้ใช้หรือเจ้าของ โปรแกรมอ่านที่ไม่สอดคล้องตามมาตรฐานซึ่งเพิกเฉยต่อ permission bits การ brute force แบบออฟไลน์ต่อรหัสผ่านที่อ่อนแอ การตรวจจับการดัดแปลง (การเข้ารหัสลับให้ confidentiality ไม่ใช่ integrity) side-channels ใน OpenSSL build ของโฮสต์ และการดูแลคีย์ ซึ่งเป็นความรับผิดชอบของผู้รวมระบบทั้งหมด การบันทึกภัยคุกคามเหล่านี้ไม่ได้ยืนยันว่าไม่มีช่องโหว่
พฤติกรรมโหมด FIPS
หัวข้อที่มีชื่อว่า “พฤติกรรมโหมด FIPS”OpenSSL build ของโฮสต์เป็นผู้ให้ cryptographic primitives ดังนั้นสถานะ FIPS จึงเป็นคุณสมบัติของโฮสต์ ไม่ใช่การตั้งค่าของไลบรารี CryptoCapabilities::detectFipsMode() คืนค่า FipsModeDetection แบบสามสถานะ (src/Security/FipsModeDetection.php): FIPS_ACTIVE, FIPS_ABSENT, หรือ INDETERMINATE ส่วนขยาย openssl ของ PHP ไม่เปิด binding สำหรับ provider model ของ OpenSSL 3 ดังนั้นการตรวจสอบจึงเป็นแบบ best-effort INDETERMINATE ถูกปฏิบัติในฐานะ “FIPS ยังไม่ได้รับการพิสูจน์” (fail-closed) ซึ่งแยกแยะได้ใน telemetry ที่ผู้ปฏิบัติงานนำไปดำเนินการได้ NextPDF ไม่ได้อ้าง FIPS 140 validation การรันบน OpenSSL ที่ผ่าน FIPS validation เป็นความรับผิดชอบของผู้ปฏิบัติงาน และผลการตรวจสอบเป็นเพียงคำแนะนำ
ความสอดคล้องตามมาตรฐาน
หัวข้อที่มีชื่อว่า “ความสอดคล้องตามมาตรฐาน”| ข้อความ | มาตรฐาน | ข้อกำหนด | รหัสอ้างอิง (reference_id) |
|---|---|---|---|
โค้ด V ใน encryption dictionary เป็นตัวเลือก encryption algorithm | ISO 32000-2 | §7.6 | |
AESV3 crypt-filter method ถูกระบุชื่อโดยรายการ CFM | ISO 32000-2 | §7.6 | |
รายการ P เป็นปริมาณ access-permission แบบ unsigned 32-bit | ISO 32000-2 | §7.6 | |
| permission bit 3 ควบคุมการพิมพ์ | ISO 32000-2 | §7.6 | |
| permission bit 6 ควบคุม annotation / form-fill | ISO 32000-2 | §7.6 | |
| การเข้ารหัสลับปกป้องเนื้อหาจากการเข้าถึงโดยไม่ได้รับอนุญาต (confidentiality) | ISO 32000-2 | §7.6 | |
| การสร้างคีย์ของ revision-6 ใช้ salted iterative hashing (Algorithm 2.B) | ISO 32000-2 | §7.6 | |
| CBC เป็นโหมด confidentiality (ไม่ใช่โหมด integrity) | NIST SP 800-38A | §6.2 | |
| initialization vector ของ CBC ต้องคาดเดาไม่ได้ | NIST SP 800-38A | App. C |
NextPDF นำข้อกำหนดที่อ้างถึงมาใช้ ไม่ได้ ยืนยันถึงการเป็นไปตามมาตรฐาน ISO 32000-2 อย่างครอบคลุมทั้งหมด การผ่านการตรวจสอบ FIPS 140 หรือการรับประกัน confidentiality ทางกฎหมายหรือทางสัญญาใด ๆ “การรองรับ standard security handler” ไม่ใช่การรับรองความปลอดภัยใน deployment ของคุณ สิ่งนั้นขึ้นอยู่กับการดูแลรหัสผ่านและนโยบายของผู้ตรวจสอบนอกไลบรารี