AES 每次处理 16 字节分组,因此 CBC 模式需要一套可逆规则来处理长度不是 16 倍数的明文。PKCS#7 通过追加字节完成填充,每个填充字节的值都表示填充长度。规则本身很简单,但错误验证可能产生严重的填充预言机漏洞。
PKCS#7 填充规则
设分组大小为 B 字节、明文长度为 L,需要添加的字节数为:
N = B - (L mod B)
追加 N 个字节,每个字节的数值都为 N。AES 的 B 固定为 16,所以 N 的取值范围是 1–16。
示例:5 字节明文
ASCII 字符串 HELLO 长 5 字节,需要追加 11 个 0x0B:
48 45 4C 4C 4F 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B 0B
解密后,最后一个字节表示追加了 11 字节。删除前必须确认最后 11 个字节全部等于 0x0B。
为什么整分组明文仍需填充
当明文长度已经是 16 的倍数时,PKCS#7 仍会添加完整填充分组:
10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
如果不添加该分组,接收方无法判断末尾的 0x01 是真实数据还是填充。PKCS#7 要求每条消息都包含填充,从而避免歧义。
这也是常见长度误区:16 字节明文使用 AES-CBC + PKCS#7 后会形成两个密文分组,此外还需单独计算 IV 的长度。
有效与无效填充
对 AES 而言,以下结尾有效:
... 01 ... 02 02 ... 03 03 03 ... 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
以下结尾无效:
... 00 # 0 不是有效填充长度 ... 02 03 # 字节与声明长度不一致 ... 11 ... # 17 超过 AES 的 16 字节分组
解码器必须拒绝无效填充。只读取最后一个值便直接裁剪,会导致数据损坏并降低安全性。
PKCS#5 与 PKCS#7 的区别
API 经常混用这两个名称,但原始定义不同:
- PKCS#5 填充最初针对 8 字节分组;
- PKCS#7/CMS 填充支持 1–255 字节分组;
- AES 分组是 16 字节,因此准确名称是 PKCS#7 填充。
部分 Java Provider 对 AES 暴露 PKCS5Padding 名称,内部实际执行通用 PKCS#7 规则。互操作时应查阅 Provider 文档,而不是只看算法字符串。
哪些 AES 模式需要填充
| 模式 | 是否需要 PKCS#7 | 原因 |
|---|---|---|
| CBC | 任意长度数据需要 | 只处理完整分组 |
| ECB | 任意长度数据需要 | 只处理完整分组 |
| CTR | 不需要 | 输出字节流 |
| GCM | 不需要 | 基于计数器的认证模式 |
| CFB / OFB | 不需要 | 以类似流密码方式运行 |
如果密码库已经自动应用 PKCS#7,不要再手工填充。双重填充会导致解填充一次后仍残留一层填充数据。
填充预言机风险
填充不等于认证。在 CBC 中,修改一个密文分组会以可预测方式改变下一个解密明文分组。如果服务会泄露填充是否有效,攻击者就能通过大量请求逐字节恢复明文,而无需知道密钥。
泄露信号可能是直接或间接的:
- 无效填充与无效 JSON 返回不同错误;
- 使用不同 HTTP 状态码;
- 响应时间存在可测差异;
- 解密失败后的连接行为不同。
首选修复方案是改用 AES-GCM 等认证加密。必须兼容 CBC 时,应采用 Encrypt-then-MAC,对版本、IV 与密文计算认证码,并在解密前验证 MAC。两种模式的差异见 AES-GCM 与 AES-CBC 对比。
安全解填充
应用代码通常应调用维护良好的密码库,而不是自己实现解填充。正确流程应当:
- 在 CBC 解密前完成密文认证;
- 确认解密结果长度非零且是分组大小的倍数;
- 读取最后一个填充长度,要求在 1–16 之间;
- 检查声明范围内每个填充字节;
- 对所有无效加密记录返回统一错误。
不要记录解密字节或详细填充失败原因。详细诊断适合隔离测试,不应出现在攻击者可观察的生产响应中。
互操作排查清单
两个系统的 AES-CBC 结果不一致时,依次确认:
- 原始密钥字节与密钥长度完全相同;
- 使用相同的 16 字节 IV;
- 明文字符编码相同;
- 都使用 PKCS#7,而不是零填充或不填充;
- 密文编码都是 Hex 或都是 Base64;
- IV 是单独传输还是统一前置;
- 没有把认证标签或 HMAC 当作密文的一部分。
字符串字符数不等于字节数。包含中文等非 ASCII 字符的 UTF-8 文本会占用更多字节,因此填充长度也不同。
长度示例
| 明文字节数 | 填充字节 | 填充后长度 |
|---|---|---|
| 0 | 16 个 0x10 | 16 |
| 1 | 15 个 0x0F | 16 |
| 15 | 1 个 0x01 | 16 |
| 16 | 16 个 0x10 | 32 |
| 31 | 1 个 0x01 | 32 |
| 32 | 16 个 0x10 | 48 |
可以使用 AES 加密工具配合测试数据比较明文、IV 与密文编码。新系统应优先选择不需要填充的认证加密模式。