加密:AES-256(CBC)與 AES-256-GCM
Core 依 ISO 32000-2:2020 §7.6 的標準安全處理常式,以 AES-256 加密 PDF。預設模式為 V=5 / R=6 / AESV3(AES-256-CBC)。可選用模式則是 ISO/TS 32003:2023 V=6 / R=7 的 AES-256-GCM 已驗證路徑。本頁說明金鑰衍生、傳輸格式、權限邊界,以及部署時必須了解的限制。
composer require nextpdf/core:^3預設路徑需要 openssl 擴充功能。AES-256-GCM 路徑會使用 openssl 或 ext-sodium。在沒有 AES-NI 硬體的主機上,libsodium 會拒絕 GCM,Core 會退回使用較慢的 OpenSSL 實作,而非降級演算法。
概念總覽
標題為「概念總覽」的區段預設處理常式是搭配 AESV3 加密過濾器的 V=5 / R=6 標準安全處理常式。呼叫 setEncryption() 時,Core 會從平台密碼學亂數來源(random_bytes())產生一把隨機的 256 位元檔案金鑰。位元組長度為 32 位元組,與 FIPS 197 的金鑰長度一致。每個物件的內容都以 AES-256-CBC 加密。依 ISO 32000-2:2020 §7.6.4 規定,16 位元組的初始化向量會前置於每段密文。
金鑰衍生依循修訂版 6 的演算法 2.B。依 ISO 32000-2:2020 §7.6.4.3.3 規定,密碼會先以 SASLprep(RFC 4013)正規化,再在字元邊界截斷至 127 個 UTF-8 位元組。衍生雜湊會透過由 AES-128-CBC 步驟驅動的反覆 SHA-256 / SHA-384 / SHA-512 程序計算,以提高離線猜測密碼的成本。使用者、擁有者與每把金鑰的鹽值,會在每個加密器執行個體建立時各產生一次,因此單一執行個體會發出決定性的字典位元組,這是多輪寫入器的前提條件。
useAesGcm() 會開啟可選用的 AES-256-GCM 路徑。它實作 ISO/TS 32003:2023 V=6 / R=7 AESV4 加密過濾器。密碼器為 AES-256-GCM,參數取自 NIST SP 800-38D。每個加密物件的傳輸配置為 12 位元組 IV、密文,接著是 16 位元組的驗證標籤。依 TS 32003 §5.2 設定檔規定,額外驗證資料為空。解密會驗證標籤,並在不符時引發 TamperedDataException;標籤驗證失敗時,絕不會傳回明文。這條路徑加入了預設 CBC 路徑本身無法提供的竄改偵測。
GCM 路徑上的 IV 唯一性紀律依循 NIST SP 800-38D §8。IV 的高位 4 位元組為每個執行個體的固定欄位,於建構時由亂數來源設定。低位 8 位元組是一個大端序計數器,每發出一個 IV 後就遞增。這與 §8.2.1 的決定性建構做法一致,只是固定欄位採隨機化以防跨文件碰撞,而非逐一列舉。第二道防護會把每個發出的 IV 記錄到碰撞集合中,若有值重複就引發 NonceReuseException。計數器溢位同樣會引發 NonceReuseException,因為溢位正是 §8 所警告的 IV 重用失效模式。
GCM 路徑適用兩項長度界限。每個物件的明文上限為 2^39 − 256 位元組,也就是 NIST SP 800-38D §5.2.1.1 推導出的每次呼叫界限。輸入若超過此上限,會引發長度例外,並提示如何跨物件分割。每把金鑰的呼叫安全界限為 2^32 次呼叫。assertWithinSafetyBound() 是一項可選用的檢查,會引發 GcmInvocationLimitExceededException,讓呼叫端在抵達 §8.3 門檻前先輪替文件金鑰。NIST SP 800-57 第 1 部分 §4 將這項金鑰生命週期決策定位為部署責任。
權限旗標屬於建議性質。位元遮罩會寫入加密後的 /Perms 項目與 /P 值,並在讀取時以 validatePerms() 還原;遇到損毀的標記時,會以安全失敗(fail closed)處理。符合規範的讀取器應遵守這些旗標。這些旗標並非由密碼學強制執行:持有解密金鑰並忽略這些位元的處理器,仍可讀取、複製或修改內容。請將權限旗標描述為一種讀取器慣例,而非存取控制。
API 介面
標題為「API 介面」的區段| 型別 | 種類 | 主要成員 | 穩定性 | 自版本 |
|---|---|---|---|---|
Aes256Encryptor | class | encrypt(), decrypt(), encryptForObject(), buildEncryptionDictionary(), verifyUserPassword(), verifyOwnerPassword(), validatePerms(), getEncryptionKey() | 穩定 | 1.0.0 |
Aes256GcmEncryptor | class | encrypt(), decrypt(), encryptStream(), assertWithinSafetyBound(), invocationCount(), isAvailable() | 穩定 | 2.18.0 |
KeyMaterial | final readonly class | generate(), exposeKey(), fingerprint() | 穩定 | 2.18.0 |
EncryptedPayloadSpec | final readonly class | toDict() | 穩定 | 2.18.0 |
CryptoCapabilities | final class | hasAesGcm(), detectFipsMode(), assertFipsAvailableForProfile() | 穩定 | 2.0.0 |
NonceReuseException | 例外 | — | 穩定 | 2.18.0 |
TamperedDataException | 例外 | — | 穩定 | 2.18.0 |
DecryptionFailedException | 例外 | — | 穩定 | 2.18.0 |
GcmInvocationLimitExceededException | 例外 | — | 穩定 | 3.0.0 |
程式碼範例 — 快速上手
標題為「程式碼範例 — 快速上手」的區段<?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');程式碼範例 — 正式環境
標題為「程式碼範例 — 正式環境」的區段<?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; } }}此密碼器會檢查主機能力、套用可選用的呼叫防護,且只記錄不可逆的金鑰指紋;偵測到竄改時,會重新拋出例外,而非傳回可疑位元組。
邊界情況與陷阱
標題為「邊界情況與陷阱」的區段- 預設的 AES-256-CBC 路徑僅提供機密性。它本身無法偵測被修改過的密文。當你需要竄改偵測時,請改用 AES-256-GCM 路徑。
useAesGcm()在 PDF/A 模式啟用時會引發例外;當openssl與ext-sodium兩者皆未提供 AES-256-GCM 時,也會引發例外。請同時攔截這兩種情況,並呈現一則維運人員可採取行動的訊息。- 在沒有 AES-NI 的主機上,libsodium 會拒絕 GCM。Core 會退回使用 OpenSSL GCM,結果正確但較慢;吞吐量會下降,安全性則不變。
- GCM 每個物件的明文上限為
2^39 − 256位元組。輸入若超過此上限,會引發長度例外;請以encryptStream()將內容分割到多個物件。 - 每個
KeyMaterial執行個體必須恰好為 32 位元組;長度錯誤時會在建構時被拒,而不會截斷。 - 讀取路徑(
verifyUserPassword()、verifyOwnerPassword()、validatePerms())會對密碼學素材使用常數時間比較,並在權限標記損毀時以安全失敗處理。
每個物件的 AES-256-CBC 加密都是一次 OpenSSL 呼叫,相對於物件主體為 O(n)。金鑰衍生會在每個加密器執行個體中執行一次反覆的演算法 2.B 程序;每份文件的成本有界且固定。AES-256-GCM 串流路徑會把輸入分割成 16 MiB 區塊,因此無論輸入總量大小,都能把活躍堆積限制在約 64 MB,遠低於已記載的 64 MB 尖峰預算。每個 GCM 物件會增加 28 位元組的額外負擔(12 位元組 IV 加 16 位元組標籤)。AES-NI 硬體能顯著提升 GCM 吞吐量;缺少它只會降低吞吐量。
安全性說明
標題為「安全性說明」的區段這個介面的威脅模型很明確。離線猜測密碼的成本,會因 SASLprep 正規化加上反覆的修訂版 6 金鑰衍生而提高,但弱密碼仍是最主要的殘餘風險。沒有任何衍生方式能消除這項風險。密文遭修改時,在 GCM 路徑上會透過標籤驗證偵測出來,但在預設的 CBC 路徑上並不會被偵測。GCM 路徑上的 IV 重用,由計數器加碰撞集合防止,與 NIST SP 800-38D §8.1 的 IV 紀律一致。計數器溢位會拒絕執行,而非回繞。透過記錄洩漏金鑰的風險,會由 KeyMaterial 遮罩以及密碼上的 #[\SensitiveParameter] 屬性緩解。在平台允許的情況下,衍生出的金鑰素材會在使用後歸零。
這條邊界同樣明確。AES-256 加密的套用方式如 ISO 32000-2:2020 §7.6 所定義,可選用路徑則依 ISO/TS 32003:2023 §5.2;實際的保護效果取決於密碼強度、金鑰管理、部署環境,以及讀取端的讀取器。權限旗標由符合規範的讀取器遵守,並非以密碼學強制執行。用於 /Perms 值的 AES-ECB 步驟,是 ISO 32000-2:2020 §7.6.4.4.10 針對單一 16 位元組區塊所強制要求的。它並不是通用的加密模式。在抵達 2^32 呼叫界限前輪替金鑰,是部署責任;Core 為此提供了一項檢查,但預設並不強制執行。
資料落地與 PII 緩解
標題為「資料落地與 PII 緩解」的區段加密與解密都在程序內執行;不會有任何文件位元組、密碼或金鑰值透過這個介面離開主機。GCM 的 IV 碰撞集合是以不可逆的金鑰指紋為鍵,而非以金鑰位元組。若部署以外部金鑰管理或 PKCS#11 權杖作為金鑰前端,該後端的資料落地問題由該部署負責;OASIS PKCS#11 v3.1 的 C_GenerateKey 是權杖內金鑰產生的契約接點。
安全遙測與記錄清理
標題為「安全遙測與記錄清理」的區段請記錄政策名稱與 8 字元的金鑰指紋,絕不要記錄金鑰或密碼。KeyMaterial::__toString() 與 __debugInfo() 會傳回經遮罩的佔位內容。來自這個介面的例外訊息會帶有一個操作標籤與一個指紋,而非金鑰位元組。GCM 呼叫次數是金鑰輪替儀表板上一項安全的遙測訊號。
威脅模型
標題為「威脅模型」的區段| 威脅 | Core 中的緩解 | 殘餘邊界 |
|---|---|---|
| 離線猜測密碼 | SASLprep 加上反覆的修訂版 6 衍生 | 弱密碼仍是最主要的風險 |
| 密文遭修改 | GCM 標籤驗證(可選用路徑) | CBC 路徑僅提供機密性 |
| IV 重用(GCM) | 隨機固定欄位加計數器加碰撞集合;溢位即拒絕 | — |
| GCM 明文過長 | 在 2^39 − 256 處進行長度檢查;提供分割指引 | 呼叫端必須以串流方式處理大量輸入 |
| 金鑰過度使用(GCM) | assertWithinSafetyBound() 於 2^32 | 可選用;預設不強制執行 |
| 權限旗標遭繞過 | 無 — 旗標屬建議性質 | 不符規範的讀取器會忽略這些旗標 |
| 透過記錄洩漏金鑰 | KeyMaterial 遮罩;#[\SensitiveParameter] | 記錄 exposeKey() 的呼叫端會破壞這項防護 |
FIPS 模式行為
標題為「FIPS 模式行為」的區段Core 並非經 FIPS 驗證的密碼學模組,也未取得 FIPS 認證。CryptoCapabilities::detectFipsMode() 是一項盡力而為的探測,會回報啟用、不存在或無法判定;assertFipsAvailableForProfile() 則會在選用 FIPS 設定檔、但主機無法證明具備 FIPS 提供者時,以安全失敗處理。當加密介面是在已載入 FIPS 驗證提供者的主機 OpenSSL 組建上執行時,它會以相容於 FIPS 的模式運作。取得經驗證、經認證的合規狀態,屬於 Enterprise 版的考量範圍。
符合性
標題為「符合性」的區段| 主張 | 標準 | 條款 | 佐證 |
|---|---|---|---|
| 每個 GCM IV 都透過決定性的固定欄位加計數器建構,於每次呼叫時保持唯一。 | NIST SP 800-38D | §8.2.1 | |
| IV 建構紀律可防止在同一把金鑰的多次呼叫間重用。 | NIST SP 800-38D | §8.1 | |
| 每個物件的明文上限與每次呼叫的長度界限相符。 | NIST SP 800-38D | §5.2.1.1 | |
| 金鑰加密週期與輪替屬於部署責任。 | NIST SP 800-57 第 1 部分修訂版 5 | §4 | |
| AES 檔案金鑰為 256 位元,與標準的金鑰長度相符。 | FIPS 197 | §4.2.1 | |
| 權杖內金鑰產生是外部金鑰儲存區的整合接點。 | OASIS PKCS#11 v3.1 標準 | C_GenerateKey(函式) |
ISO 32000-2:2020 §7.6 與 ISO/TS 32003:2023 §5.2 是本頁所述處理常式的規範性依據。它們的文字受授權限制。本頁以自己的話改寫這些內容,並以條號引用,未引述其中任何原文。位元組精確金鑰衍生的已驗證執行階段佐證,是演算法 2.B 標準測試,以及頁面佐證結尾所列的外部 oracle(測試載具)夾具。
商業脈絡
標題為「商業脈絡」的區段Core 同時提供預設的 AES-256-CBC 路徑與可選用的 AES-256-GCM 路徑,搭配本機金鑰介面與加密政策 gate。Enterprise 版在相同契約之後,再加上 HSM/PKCS#11 金鑰保管後端,以及 FIPS 模式的加密政策設定檔。公開 API 完全相同,差別在於金鑰保管後端與政策實作。
另請參閱
標題為「另請參閱」的區段- Security — 安全模組總覽與權限邊界。
- Contracts / Security Policy — 為密碼器把關的加密政策契約。
- Security / Signing — 簽章與時間戳記,相鄰的密碼學介面。
- Conformance — PDF/A 對
Encrypt鍵的禁用規定。