AES-CBC 模式与 PKCS#7 填充实践
这篇文章记录 Cryptopals Set 2 中几道和 AES 分组密码有关的题。重点不是背概念,而是通过真实数据把下面几件事跑通:
PKCS#7 padding 怎么补 |
实验数据来自 Cryptopals 平台,尤其是 Challenge 10 的真实密文文件:
https://cryptopals.com/static/challenge-data/10.txt |
1. 为什么分组密码需要 padding
AES 的 block size 是 16 字节。CBC、ECB 等模式都要按块处理数据。
如果明文长度不是 16 的倍数,就需要填充。Cryptopals Challenge 9 要求实现 PKCS#7 padding。
题目样例:
YELLOW SUBMARINE |
填充到 20 字节:
YELLOW SUBMARINE\x04\x04\x04\x04 |
因为原文长度是 16,目标长度是 20,所以补 4 个字节,每个字节的值都是 0x04。
实现:
def pkcs7_pad(data: bytes, block_size: int) -> bytes: |
运行截图:

输出里可以看到:
Output hex: 59454c4c4f57205355424d4152494e4504040404 |
2. PKCS#7 去填充必须校验
去 padding 时不能只看最后一个字节,然后直接切掉。必须确认最后 pad_len 个字节都等于 pad_len。
正确写法:
def pkcs7_unpad(data: bytes, block_size: int = 16) -> bytes: |
Cryptopals Challenge 15 的三个样例:
"ICE ICE BABY\x04\x04\x04\x04" -> valid |
运行截图:

3. CBC 模式原理
CBC 是 Cipher Block Chaining。它的关键是:每一块都依赖前一块密文。
加密:
C_i = AES_ENC(P_i xor C_{i-1}, K) |
解密:
P_i = AES_DEC(C_i, K) xor C_{i-1} |
第一块没有前一块密文,所以使用 IV:
C_0 = IV |
Cryptopals Challenge 10 给出的参数:
key = YELLOW SUBMARINE |
4. 自己实现 AES-CBC 解密
可以使用库函数做 AES 单块 ECB 解密,但 CBC 链接过程自己写,这样更容易理解公式。
def xor_bytes(left: bytes, right: bytes) -> bytes: |
运行:
python work\experiment_02_aes_modes\02_decrypt_aes_cbc_mode.py |
真实 VSCode 截图:

关键输出:
Ciphertext length: 2880 bytes |
这说明我们自己写的 CBC 链接过程和平台数据匹配。
5. ECB/CBC oracle 检测
Challenge 11 要求写一个加密 oracle,它随机选择 ECB 或 CBC,然后我们根据密文判断模式。
检测 ECB 的方法是看重复密文块:
def detect_ecb(ciphertext: bytes, block_size: int = 16) -> bool: |
为什么可行?
ECB: 相同明文块 -> 相同密文块 |
所以输入很多重复的 A:
actual, ciphertext = encryption_oracle(b"A" * 64) |
运行截图:

本次真实运行检测准确率为:
12/12 |
6. CBC bit flipping 为什么可行
CBC 解密公式:
P_i = AES_DEC(C_i) xor C_{i-1} |
如果攻击者修改 C_{i-1} 的某些 bit,那么解密后的 P_i 对应 bit 也会改变。
这就是 Challenge 16 的 bit flipping 攻击。
目标是在明文中构造:
;admin=true; |
但是输入会过滤 ; 和 =。做法是先输入安全字符,再在上一块密文中翻转差值。
核心思路:
delta = original_byte ^ target_byte |
运行截图:

结果:
Admin before bit flip: False |
7. 单元测试
实验二的单元测试覆盖:
PKCS#7 padding |
截图:

8. 常见错误
8.1 解密时把 XOR 顺序写错
CBC 解密是:
AES_DEC(C_i) xor C_{i-1} |
不是先把 C_i 和 C_{i-1} XOR 再 AES 解密。
8.2 忘记更新 prev
每处理完一个块,prev 要更新为当前密文块:
prev = block |
不能更新成明文块。
8.3 padding 只切不验
错误 padding 必须抛异常,否则后续 padding oracle 或格式绕过类问题会更容易出现。
8.4 把 IV 当成密钥
IV 参与第一块 CBC 运算,但 IV 不是 key。key 要保密,IV 通常可以公开但不能随便复用到危险场景里。
9. 小结
这组 AES/CBC 实验可以按下面顺序掌握:
先写 PKCS#7 padding |
分组密码的安全不只取决于 AES 算法本身,也取决于模式、填充和输入处理是否正确。
参考资料
- Cryptopals Set 2: https://cryptopals.com/sets/2
- Cryptopals Challenge 10 data: https://cryptopals.com/static/challenge-data/10.txt
- NIST SP 800-38A, Recommendation for Block Cipher Modes of Operation
- PKCS#7 / CMS padding 相关资料