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

การเข้ารหัสลับ: AES-256 (CBC) และ AES-256-GCM

Core เข้ารหัสลับไฟล์ Portable Document Format (PDF) ด้วย AES-256 (Advanced Encryption Standard ที่ใช้คีย์ขนาด 256 บิต) ภายใต้ Standard security handler ตาม ISO 32000-2:2020 §7.6 โหมดเริ่มต้นคือ V=5 / R=6 / AESV3 (AES-256-CBC, Cipher Block Chaining) โหมดที่มีการยืนยันความถูกต้องและเปิดใช้ตามต้องการคือเส้นทาง ISO/TS 32003:2023 V=6 / R=7 AES-256-GCM (Galois/Counter Mode) หน้านี้อธิบายการอนุพันธ์คีย์ รูปแบบ wire ขอบเขตของสิทธิ์ และข้อจำกัดในการนำไปใช้งาน

Terminal window
composer require nextpdf/core:^3

เส้นทางเริ่มต้นต้องใช้ส่วนขยาย openssl ส่วนเส้นทาง AES-256-GCM ใช้ openssl หรือ ext-sodium บนโฮสต์ที่ไม่มีฮาร์ดแวร์ AES-NI นั้น libsodium จะปฏิเสธ GCM และ Core จะถอยกลับไปทำงานผ่าน OpenSSL ที่ช้ากว่าโดยไม่เปลี่ยนอัลกอริทึม

ตัวจัดการเริ่มต้นใช้ Standard security handler แบบ V=5 / R=6 ร่วมกับ crypt filter แบบ AESV3 เมื่อเรียก setEncryption() Core จะสร้างคีย์ไฟล์ขนาด 256 บิตแบบสุ่มจากแหล่งสุ่มเชิงเข้ารหัสลับของแพลตฟอร์ม (random_bytes()) คีย์มีขนาด 32 ไบต์ ซึ่งตรงกับความยาวคีย์ตาม FIPS 197 Core เข้ารหัสลับเนื้อหาแยกตาม object ด้วย AES-256-CBC โดยวาง initialization vector ขนาด 16 ไบต์ไว้ด้านหน้า ciphertext แต่ละชุด ตามที่ ISO 32000-2:2020 §7.6.4 กำหนด

การอนุพันธ์คีย์เป็นไปตาม Algorithm 2.B ของ revision 6 Core จะ normalize รหัสผ่านด้วย SASLprep (RFC 4013) ก่อน แล้วตัดให้เหลือ 127 ไบต์ UTF-8 ที่ขอบเขตอักขระ ตามที่ ISO 32000-2:2020 §7.6.4.3.3 กำหนด Core จะคำนวณแฮชที่อนุพันธ์ด้วยรูทีน SHA-256 / SHA-384 / SHA-512 แบบวนซ้ำ ซึ่งขับเคลื่อนด้วยขั้นตอน AES-128-CBC เพื่อเพิ่มต้นทุนในการเดารหัสผ่านแบบ offline Core จะสร้าง salt ของผู้ใช้ ของเจ้าของ และของแต่ละคีย์เพียงครั้งเดียวต่ออินสแตนซ์ของ encryptor ดังนั้นอินสแตนซ์หนึ่งจึงให้ไบต์ dictionary ที่กำหนดซ้ำได้ ซึ่งเป็นเงื่อนไขตั้งต้นสำหรับ writer แบบหลายรอบ

useAesGcm() เปิดใช้เส้นทาง AES-256-GCM แบบเลือกใช้ เส้นทางนี้ใช้ crypt filter แบบ AESV4 ตาม ISO/TS 32003:2023 V=6 / R=7 cipher คือ AES-256-GCM โดยใช้พารามิเตอร์จาก NIST SP 800-38D สำหรับ object ที่เข้ารหัสลับแต่ละตัว รูปแบบ wire คือ IV ขนาด 12 ไบต์ ตามด้วย ciphertext และ authentication tag ขนาด 16 ไบต์ additional authenticated data จะเป็นค่าว่าง ตามที่โปรไฟล์ TS 32003 §5.2 กำหนด การถอดรหัสจะตรวจสอบ tag และยก TamperedDataException เมื่อค่าไม่ตรงกัน โดยจะไม่คืนค่า plaintext หลังการตรวจสอบ tag ล้มเหลว เส้นทางนี้เพิ่มการตรวจจับการแก้ไข ซึ่งเส้นทาง CBC เริ่มต้นไม่ได้ให้ไว้เอง

เส้นทาง GCM เป็นไปตามวินัยเรื่อง IV ที่ไม่ซ้ำกันใน NIST SP 800-38D §8 โดย 4 ไบต์บนของ IV เป็นฟิลด์คงที่รายอินสแตนซ์ที่ตั้งค่าจากแหล่งสุ่มระหว่างการสร้าง และ 8 ไบต์ล่างเป็นตัวนับแบบ big-endian ที่เพิ่มขึ้นหลังออก IV แต่ละครั้ง แนวทางนี้เป็นไปตามวิธีการสร้างแบบกำหนดได้ใน §8.2.1 ยกเว้นว่าฟิลด์คงที่จะถูกสุ่มเพื่อป้องกันการชนกันข้ามเอกสาร แทนที่จะใช้การแจกแจง ตัวป้องกันอีกชั้นจะบันทึก IV ทุกตัวที่ออกไว้ในชุดตรวจจับการชน และยก NonceReuseException หากพบค่าซ้ำ การล้นของตัวนับก็ยก NonceReuseException เช่นกัน เพราะการล้นคือรูปแบบความล้มเหลวจากการใช้ IV ซ้ำที่ §8 เตือนไว้

เส้นทาง GCM มีขอบเขตความยาวสองค่า เพดาน plaintext ราย object คือ 2^39 − 256 ไบต์ ซึ่งเป็นขอบเขตต่อการเรียกที่อนุพันธ์ไว้ใน NIST SP 800-38D §5.2.1.1 อินพุตที่ใหญ่กว่าจะยก length exception พร้อมคำแนะนำให้แบ่งข้อมูลข้าม object หลายตัว ขอบเขตความปลอดภัยของการเรียกคือ 2^32 ครั้งต่อคีย์ assertWithinSafetyBound() เป็นการตรวจสอบแบบเลือกเปิดใช้ซึ่งยก GcmInvocationLimitExceededException เพื่อให้ผู้เรียกหมุนเวียนคีย์เอกสารได้ก่อนถึงเกณฑ์ตาม §8.3 NIST SP 800-57 Part 1 §4 ถือว่าการตัดสินใจเรื่องอายุการใช้งานคีย์นี้เป็นความรับผิดชอบของการนำไปใช้งาน

แฟล็กสิทธิ์เป็นเพียงคำแนะนำ Core เขียน bitmask ลงในรายการ /Perms ที่เข้ารหัสลับและค่า /P จากนั้นกู้คืนด้วย validatePerms() เมื่ออ่าน ซึ่งจะ fail closed เมื่อพบ marker ที่เสียหาย reader ที่สอดคล้องควรปฏิบัติตามแฟล็กเหล่านี้ แต่แฟล็กเหล่านี้ ไม่ ได้ถูกบังคับใช้ด้วยการเข้ารหัสลับ: โปรเซสเซอร์ที่มีคีย์ถอดรหัสและละเลยบิตเหล่านี้ยังสามารถอ่าน คัดลอก หรือแก้ไขเนื้อหาได้ จงอธิบายแฟล็กสิทธิ์ว่าเป็นข้อตกลงของ reader ไม่ใช่การควบคุมการเข้าถึง

ชนิดประเภทสมาชิกหลักความเสถียรตั้งแต่
Aes256Encryptorclassencrypt(), decrypt(), encryptForObject(), buildEncryptionDictionary(), verifyUserPassword(), verifyOwnerPassword(), validatePerms(), getEncryptionKey()stable1.0.0
Aes256GcmEncryptorclassencrypt(), decrypt(), encryptStream(), assertWithinSafetyBound(), invocationCount(), isAvailable()stable2.18.0
KeyMaterialfinal readonly classgenerate(), exposeKey(), fingerprint()stable2.18.0
EncryptedPayloadSpecfinal readonly classtoDict()stable2.18.0
CryptoCapabilitiesfinal classhasAesGcm(), detectFipsMode(), assertFipsAvailableForProfile()stable2.0.0
NonceReuseExceptionexceptionstable2.18.0
TamperedDataExceptionexceptionstable2.18.0
DecryptionFailedExceptionexceptionstable2.18.0
GcmInvocationLimitExceededExceptionexceptionstable3.0.0
examples/22-protection.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
// AES-256-CBC, V=5/R=6. Call before addPage().
$doc->setEncryption(
userPassword: 'demo',
ownerPassword: 'admin',
permissions: 4, // printing only; copy/modify denied for a conforming reader
);
$doc->addPage();
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 8, 'Confidential', newLine: true);
$doc->save(__DIR__ . '/output/22-protection.pdf');
examples/security/gcm-authenticated-encryption.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Security\CryptoCapabilities;
use NextPDF\Security\Encryption\Aes256GcmEncryptor;
use NextPDF\Security\Exception\TamperedDataException;
use NextPDF\Security\KeyMaterial;
use Psr\Log\LoggerInterface;
final readonly class AuthenticatedBlobCipher
{
public function __construct(private LoggerInterface $logger) {}
/**
* Seal a payload with AES-256-GCM and return the wire-format bytes.
*
* @param non-empty-string $plaintext The payload to protect.
*
* @return non-empty-string IV(12) || ciphertext || tag(16).
*/
public function seal(string $plaintext, KeyMaterial $key): string
{
if (!CryptoCapabilities::hasAesGcm()) {
throw new \RuntimeException('Host cannot perform AES-256-GCM.');
}
$cipher = new Aes256GcmEncryptor($key);
// Opt-in NIST SP 800-38D §8.3 key-rotation guard.
$cipher->assertWithinSafetyBound();
$wire = $cipher->encrypt($plaintext);
$this->logger->info('Payload sealed', [
'key_fingerprint' => $key->fingerprint(),
'invocations' => $cipher->invocationCount(),
]);
return $wire;
}
/**
* Open a sealed payload; a modified payload raises, never returns plaintext.
*
* @param non-empty-string $wire IV(12) || ciphertext || tag(16).
*/
public function open(string $wire, KeyMaterial $key): string
{
try {
return (new Aes256GcmEncryptor($key))->decrypt($wire);
} catch (TamperedDataException $e) {
$this->logger->warning('Tampered payload rejected', [
'key_fingerprint' => $key->fingerprint(),
]);
throw $e;
}
}
}

cipher ตรวจสอบความสามารถของโฮสต์ ใช้ตัวป้องกันจำนวนการเรียกแบบเลือกเปิดใช้ บันทึกเฉพาะ key fingerprint ที่ย้อนกลับไม่ได้ และส่งต่อ tamper rejection แทนที่จะคืนค่าไบต์ที่น่าสงสัย

  • เส้นทาง AES-256-CBC เริ่มต้นให้เฉพาะการรักษาความลับเท่านั้น เส้นทางนี้ไม่ตรวจจับ ciphertext ที่ถูกแก้ไขเอง ใช้เส้นทาง AES-256-GCM เมื่อต้องการตรวจจับการแก้ไข
  • useAesGcm() จะยกข้อยกเว้นเมื่อโหมด PDF/A กำลังทำงานอยู่ และเมื่อทั้ง openssl และ ext-sodium ไม่มี AES-256-GCM ให้ใช้ จงดักทั้งสองกรณีและแสดงข้อความที่ผู้ปฏิบัติงานนำไปดำเนินการต่อได้
  • บนโฮสต์ที่ไม่มี AES-NI นั้น libsodium จะปฏิเสธ GCM Core จะถอยกลับไปใช้ OpenSSL GCM ซึ่งถูกต้องแต่ช้ากว่า อัตราการประมวลผลจะลดลง แต่ความปลอดภัยไม่ลดลง
  • เพดาน plaintext ราย object ของ GCM คือ 2^39 − 256 ไบต์ อินพุตที่ใหญ่กว่าจะยก length exception จงแบ่งเนื้อหาข้าม object หลายตัวด้วย encryptStream() แทน
  • อินสแตนซ์ KeyMaterial ต้องมีขนาด 32 ไบต์พอดี การสร้างจะปฏิเสธความยาวที่ไม่ถูกต้องแทนที่จะตัดทิ้ง
  • เส้นทางการอ่าน (verifyUserPassword(), verifyOwnerPassword(), validatePerms()) ใช้การเปรียบเทียบแบบ constant-time สำหรับข้อมูลเชิงเข้ารหัสลับ และ fail closed เมื่อพบ permission marker ที่เสียหาย

การเข้ารหัสลับ AES-256-CBC ราย object เป็นการเรียก OpenSSL หนึ่งครั้ง และมีความซับซ้อน O(n) ตามขนาดเนื้อหาของ object การอนุพันธ์คีย์รันรูทีน Algorithm 2.B แบบวนซ้ำหนึ่งครั้งต่ออินสแตนซ์ของ encryptor ต้นทุนถูกจำกัดและคงที่ต่อเอกสาร เส้นทาง streaming แบบ AES-256-GCM จะแบ่งอินพุตเป็นก้อนขนาด 16 MiB จำกัดการใช้ heap ที่ใช้งานอยู่ไว้ที่ราว 64 MB ไม่ว่าขนาดอินพุตรวมจะเป็นเท่าใด และอยู่ภายในงบ peak 64 MB ที่ระบุไว้ object ของ GCM แต่ละตัวเพิ่ม overhead 28 ไบต์ (IV ขนาด 12 ไบต์ บวก tag ขนาด 16 ไบต์) ฮาร์ดแวร์ AES-NI ช่วยเพิ่มอัตราการประมวลผลของ GCM อย่างมีนัยสำคัญ หากไม่มี จะมีเพียงอัตราการประมวลผลที่ลดลง

พื้นผิวการเข้ารหัสลับนี้มีโมเดลภัยคุกคามที่ชัดเจน การ normalize ด้วย SASLprep ร่วมกับการอนุพันธ์คีย์แบบ revision-6 ที่วนซ้ำช่วยเพิ่มต้นทุนในการเดารหัสผ่านแบบ offline แต่รหัสผ่านที่อ่อนแอยังคงเป็นความเสี่ยงตกค้างหลัก ไม่มีการอนุพันธ์ใดขจัดความเสี่ยงนั้นได้ เส้นทาง GCM ตรวจจับการแก้ไข ciphertext ผ่านการตรวจสอบ tag แต่เส้นทาง CBC เริ่มต้นทำ ไม่ ได้ บนเส้นทาง GCM ตัวนับร่วมกับชุดตรวจจับการชนช่วยป้องกันการใช้ IV ซ้ำ สอดคล้องกับวินัย IV ตาม NIST SP 800-38D §8.1 การล้นของตัวนับจะถูกปฏิเสธแทนที่จะวนกลับ การ redact KeyMaterial และแอตทริบิวต์ #[\SensitiveParameter] บนรหัสผ่านช่วยลดการเปิดเผยคีย์ผ่าน log ข้อมูลคีย์ที่อนุพันธ์จะถูกล้างเป็นศูนย์หลังใช้งานในที่ที่แพลตฟอร์มอนุญาต

ขอบเขตก็ชัดเจนเช่นกัน Core ใช้การเข้ารหัสลับ AES-256 ตามที่กำหนดใน ISO 32000-2:2020 §7.6 และสำหรับเส้นทางแบบเลือกเปิดใช้คือ ISO/TS 32003:2023 §5.2 การป้องกันจะได้ผลเพียงใดขึ้นอยู่กับความแข็งแกร่งของรหัสผ่าน การจัดการคีย์ สภาพแวดล้อมการนำไปใช้งาน และ reader ที่ใช้งาน reader ที่สอดคล้องจะปฏิบัติตามแฟล็กสิทธิ์ แต่การเข้ารหัสลับไม่ได้บังคับใช้แฟล็กเหล่านั้น ขั้นตอน AES-ECB ที่ใช้สำหรับค่า /Perms ถูกกำหนดโดย ISO 32000-2:2020 §7.6.4.4.10 สำหรับบล็อกขนาด 16 ไบต์เดียว ขั้นตอนนี้ไม่ใช่โหมดสำหรับใช้งานทั่วไป Core เปิดเผยการตรวจสอบสำหรับการหมุนเวียนคีย์ก่อนถึงขอบเขตการเรียก 2^32 แต่ไม่บังคับใช้ตามค่าเริ่มต้น การหมุนเวียนนั้นเป็นความรับผิดชอบของการนำไปใช้งาน

การเข้ารหัสลับและการถอดรหัสรันภายในกระบวนการ ไม่มีไบต์เอกสาร รหัสผ่าน หรือค่าคีย์ออกจากโฮสต์ผ่านพื้นผิวนี้ ชุดตรวจจับการชนของ IV ใน GCM ใช้ key fingerprint ที่ย้อนกลับไม่ได้เป็นคีย์ ไม่ใช่ไบต์ของคีย์ หากการนำไปใช้งานวางคีย์ไว้หลังระบบจัดการคีย์ภายนอกหรือ token แบบ PKCS#11 backend นั้นจะรับผิดชอบเรื่องการคงถิ่น OASIS PKCS#11 v3.1 C_GenerateKey คือจุดสัญญาสำหรับการสร้างคีย์ที่อยู่ภายใน token

จงบันทึกชื่อนโยบายและ key fingerprint ขนาด 8 อักขระ อย่าบันทึกคีย์หรือรหัสผ่าน KeyMaterial::__toString() และ __debugInfo() จะคืนค่า placeholder ที่ถูก redact ข้อยกเว้นจากพื้นผิวนี้มีป้ายกำกับการดำเนินการและ fingerprint ไม่ใช่ไบต์ของคีย์ จำนวนการเรียก GCM เป็นเทเลเมทรีที่ปลอดภัยสำหรับแดชบอร์ดหมุนเวียนคีย์

ภัยคุกคามการลดทอนใน Coreขอบเขตตกค้าง
การเดารหัสผ่านแบบ offlineSASLprep ร่วมกับการอนุพันธ์แบบ revision-6 ที่วนซ้ำรหัสผ่านที่อ่อนแอยังคงเป็นความเสี่ยงหลัก
การแก้ไข ciphertextการตรวจสอบ GCM tag (เส้นทางที่เปิดใช้ตามต้องการ)เส้นทาง CBC ให้เฉพาะการรักษาความลับ
การใช้ IV ซ้ำ (GCM)ฟิลด์คงที่แบบสุ่ม ร่วมกับตัวนับและชุดตรวจจับการชน การล้นจะถูกปฏิเสธ
plaintext ของ GCM ที่ยาวเกินไปการตรวจสอบความยาวที่ 2^39 − 256 คำแนะนำการแบ่งส่วนผู้เรียกต้อง stream อินพุตขนาดใหญ่
การใช้คีย์มากเกินไป (GCM)assertWithinSafetyBound() ที่ 2^32เปิดใช้ตามต้องการ ไม่บังคับใช้ตามค่าเริ่มต้น
การข้ามแฟล็กสิทธิ์ไม่มี — แฟล็กเป็นเพียงคำแนะนำreader ที่ไม่สอดคล้องจะละเลยแฟล็ก
การเปิดเผยคีย์ผ่าน logKeyMaterial การ redact; #[\SensitiveParameter]ผู้เรียกที่บันทึก exposeKey() จะทำลายการป้องกันนี้

Core ไม่ใช่โมดูลเชิงเข้ารหัสลับที่ผ่านการตรวจรับรอง FIPS และไม่ได้รับการรับรอง FIPS CryptoCapabilities::detectFipsMode() เป็นการตรวจสอบแบบ best-effort ที่รายงานสถานะ active, absent หรือ indeterminate assertFipsAvailableForProfile() จะ fail closed เมื่อเลือกโปรไฟล์ FIPS บนโฮสต์ที่ไม่สามารถพิสูจน์ FIPS provider ได้ พื้นผิวการเข้ารหัสลับทำงานในโหมดที่เข้ากันได้กับ FIPS เมื่อรันบนโฮสต์ที่มี OpenSSL build ซึ่งโหลด FIPS-validated provider ไว้ สถานะที่ผ่านการตรวจรับรองและได้รับการรับรองเป็นเรื่องของรุ่น Enterprise

การกล่าวอ้างมาตรฐานข้อกำหนดหลักฐาน
IV ของ GCM ทุกตัวไม่ซ้ำกันต่อการเรียกแต่ละครั้งผ่านการสร้างแบบ fixed-field ร่วมกับตัวนับที่กำหนดได้NIST SP 800-38D§8.2.1
วินัยการสร้าง IV ป้องกันการใช้ซ้ำข้ามการเรียกบนคีย์เดียวNIST SP 800-38D§8.1
เพดาน plaintext ราย object ตรงกับขอบเขตความยาวต่อรายการเรียกNIST SP 800-38D§5.2.1.1
อายุการใช้งานคีย์เชิงการเข้ารหัสลับและการหมุนเวียนเป็นความรับผิดชอบของการนำไปใช้งานNIST SP 800-57 ส่วนที่ 1 ฉบับแก้ไขที่ 5§4
คีย์ไฟล์ AES มีขนาด 256 บิต ตรงกับความยาวคีย์ของมาตรฐานFIPS 197§4.2.1
การสร้างคีย์ที่อยู่ใน token คือจุดผสานรวมกับที่เก็บคีย์ภายนอกOASIS PKCS#11 v3.1C_GenerateKey

ISO 32000-2:2020 §7.6 และ ISO/TS 32003:2023 §5.2 เป็นพื้นฐานเชิงบรรทัดฐานสำหรับตัวจัดการที่บันทึกไว้ในที่นี้ ข้อความของมาตรฐานเหล่านั้นมีข้อจำกัดด้านใบอนุญาต หน้านี้จึงสรุปสาระของมาตรฐานเหล่านั้น อ้างอิงข้อกำหนดด้วยหมายเลข และไม่ได้คัดลอกข้อความใดเลย การทดสอบมาตรฐาน Algorithm 2.B และ fixture แบบ external-oracle ใน evidence trailer ของหน้านี้ให้หลักฐานรันไทม์ที่ได้รับการตรวจสอบสำหรับการอนุพันธ์คีย์ที่ตรงระดับไบต์

Core มาพร้อมเส้นทาง AES-256-CBC เริ่มต้น เส้นทาง AES-256-GCM แบบเลือกเปิดใช้ พื้นผิว local-key และ gate ของ crypto-policy รุ่น Enterprise เพิ่ม backend การดูแลคีย์แบบ HSM/PKCS#11 และโปรไฟล์ crypto-policy โหมด FIPS ภายใต้สัญญาเดียวกัน application programming interface (API) สาธารณะเหมือนกัน แต่ backend การดูแลคีย์และการบังคับใช้นโยบายแตกต่างกัน

  • Security — ภาพรวมของโมดูลความปลอดภัยและขอบเขตของสิทธิ์
  • Contracts / Security Policy — สัญญา crypto-policy ที่ควบคุม cipher
  • Security / Signing — ลายเซ็นและการประทับเวลา พื้นผิวการเข้ารหัสลับที่เกี่ยวข้องกัน
  • Conformance — ข้อห้ามของ PDF/A ต่อ Encrypt ในฐานะคีย์