PKCS#7 填充是什么
PKCS#7 填充是分组密码在工作模式中把数据补齐到整块的规则:缺几个字节就填几个「值等于缺失字节数」的字节;数据本来就对齐时追加一整个块,保证解密端一定能识别填充。TLS 1.0-1.2 的 CBC 套件与大量应用层的 AES-CBC 用法都采用这套规则,解密后必须校验填充是否合法。
基本信息
| 适用场景 | 分组模式的数据对齐(AES-CBC、AES-ECB、SM4-CBC) |
|---|---|
| 填充值 | 0x01 到 0x10,数值等于填充字节数 |
| 块长 | AES/SM4 为 16 字节,DES/3DES 为 8 字节 |
| 整块情况 | 追加 16 个 0x10 |
| 校验要求 | 解密后逐字节校验填充值一致 |
典型使用场景
- AES-CBC 加解密:调用库时区分 PKCS7 填充与无填充两种模式
- 对接第三方接口:密文长度不是块长整数倍说明使用了填充
- 协议实现:TLS 1.2 的 CBC 套件中填充长度参与 MAC 计算
- 数据迁移:从带填充的旧格式迁到 AEAD 时需显式去掉填充
- 排查解密报错:填充非法是「解密失败」类报错最常见的原因之一
库中的填充与去除
库中的填充与去除
from cryptography.hazmat.primitives import padding # 提供 PKCS7 填充器
padder = padding.PKCS7(128).padder() # 128 位 = 16 字节块
padded = padder.update(data) + padder.finalize()
unpadder = padding.PKCS7(128).unpadder()
plain = unpadder.update(padded) + unpadder.finalize() # 填充非法会抛异常填充示例(块长 16 字节)
| 原始长度 | 填充字节 | 结果长度 |
|---|---|---|
| 13 | 03 03 03 | 16 |
| 15 | 01 | 16 |
| 16 | 10 共 16 个 | 32 |
| 0 | 10 共 16 个 | 16 |
PKCS#5 在规范上只定义 8 字节块(DES)的填充,AES 场景实际使用的是 PKCS#7 的推广形式;很多库与文档把二者名称混用,行为一致,Java 的 PKCS5Padding 在 AES 下也按 16 字节块处理。
填充是否合法只能通过校验判断,不能只按长度截断:填充预言攻击正是利用解密端对填充合法性的差异化反馈,因此错误信息与响应耗时都应保持一致。
常见实现差异
- Python cryptography 的 PKCS7 需要显式指定块位数,128 位对应 16 字节
- Java 的 AES/CBC/PKCS5Padding 实际按 16 字节块填充
- Go 标准库不提供填充,需自行实现或引入成熟库
- Node.js 的 createCipheriv 要求调用方自行处理填充与去填充
填充长度字段的取值介于 1 与块长之间;解密端校验时只要发现某个字节的值超过块长或前后不一致,就应判定整块非法并丢弃,不能返回部分明文。