加密: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 实现,而不会降级算法。
概念总览
标题为“概念总览”的章节默认处理程序是 V=5 / R=6 标准安全处理程序,并搭配 AESV3 加密过滤器。调用 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) | 在 2^32 处由 assertWithinSafetyBound() 检查 | 可选;默认不强制执行 |
| 权限标志遭绕过 | 无 — 标志属建议性质 | 不符合规范的读取器会忽略这些标志 |
| 经由日志泄漏密钥 | 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键的禁用规定。