ข้ามไปยังเนื้อหา

เข้ารหัสลับ 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”

Terminal window
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 ตามที่บันทึกไว้

NextPDF\Core\Concerns\HasSecurity (ผสานเข้ากับ Document):

  • setEncryption(#[SensitiveParameter] string $userPassword, #[SensitiveParameter] string $ownerPassword = '', int $permissions = -1): static — กำหนดค่าการเข้ารหัสลับแบบ standard-handler ด้วย AES-256 permissions = -1 ให้สิทธิ์ทั้งหมด เมื่อ ownerPassword ว่างเปล่า ระบบจะนำรหัสผ่านผู้ใช้มาใช้ซ้ำเป็นรหัสผ่านเจ้าของ เรียกก่อน addPage().
  • getEncryptor(): ?Aes256Encryptor — ตัวเข้ารหัสลับที่กำหนดค่าไว้ หรือ null
  • useAesGcm(?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)ค่าการดำเนินการ
34พิมพ์เอกสาร
48แก้ไขเนื้อหาเอกสาร
516คัดลอก / สกัดข้อความและกราฟิก
632เพิ่มหรือแก้ไข 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 บนโฮสต์มีให้ มิฉะนั้นจะ throw InvalidConfigException การเข้ารหัสลับนี้เข้ากันไม่ได้กับ 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 แต่คุณยังต้องไม่ให้รหัสผ่านปรากฏในล็อก ข้อความข้อยกเว้น และรายงานข้อขัดข้องของคุณเอง

ไลบรารีดำเนินการเข้ารหัสลับภายในกระบวนการ ไลบรารีไม่ส่งเอกสารหรือรหัสผ่านไปที่ใด เอนจินไม่เขียนรหัสผ่าน คีย์ หรือไบต์ของเอกสารลงดิสก์ ยกเว้นผลลัพธ์ที่เข้ารหัสลับแล้วซึ่งคุณบันทึก ตำแหน่งของไฟล์ผลลัพธ์และวิธีเก็บรหัสผ่านเป็นข้อพิจารณาด้าน deployment ที่ผู้รวมระบบเป็นเจ้าของ ไลบรารีไม่รับประกันถิ่นที่อยู่ของข้อมูล หากเอกสาร plaintext มีข้อมูลส่วนบุคคล ข้อมูลนั้นได้รับการปกป้องเพียงเท่ากับรหัสผ่านที่อ่อนแอที่สุดและข้อควรระวังเรื่อง cooperating-reader ข้างต้น การเข้ารหัสลับไม่ใช่สิ่งทดแทนการลดข้อมูลที่ระบุตัวบุคคลได้ (PII) ที่คุณใส่ไว้ในเอกสาร

การเข้ารหัสลับปล่อย 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 ของโฮสต์ และการดูแลคีย์ ซึ่งเป็นความรับผิดชอบของผู้รวมระบบทั้งหมด การบันทึกภัยคุกคามเหล่านี้ไม่ได้ยืนยันว่าไม่มีช่องโหว่

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 algorithmISO 32000-2§7.6
AESV3 crypt-filter method ถูกระบุชื่อโดยรายการ CFMISO 32000-2§7.6
รายการ P เป็นปริมาณ access-permission แบบ unsigned 32-bitISO 32000-2§7.6
permission bit 3 ควบคุมการพิมพ์ISO 32000-2§7.6
permission bit 6 ควบคุม annotation / form-fillISO 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-38AApp. C

NextPDF นำข้อกำหนดที่อ้างถึงมาใช้ ไม่ได้ ยืนยันถึงการเป็นไปตามมาตรฐาน ISO 32000-2 อย่างครอบคลุมทั้งหมด การผ่านการตรวจสอบ FIPS 140 หรือการรับประกัน confidentiality ทางกฎหมายหรือทางสัญญาใด ๆ “การรองรับ standard security handler” ไม่ใช่การรับรองความปลอดภัยใน deployment ของคุณ สิ่งนั้นขึ้นอยู่กับการดูแลรหัสผ่านและนโยบายของผู้ตรวจสอบนอกไลบรารี