第3章 AES算法参数详解:密钥长度、模式(ECB/CBC/CFB/OFB/CTR)、IV与填充
大家在实际对接AES时,最头疼的往往不是算法本身——而是那一堆参数该怎么选。密钥长度、工作模式、IV、填充方式,任意一个配错了,两端就解不出来。这一章我把自己踩过的坑和常用的方案整理出来,帮你快速搞定参数调试。
3.1 密钥长度:AES-128 / 192 / 256 到底差多少?
先看一组官方参数,心里有个底:
| 密钥长度 | 轮数 | 密钥扩展量 | 安全强度(等效于RSA) | 性能相对比 |
|---|---|---|---|---|
| AES-128 | 10 轮 | 44 字 | ≈ RSA-3072 | 最快 |
| AES-192 | 12 轮 | 52 字 | ≈ RSA-7680 | 中等 |
| AES-256 | 14 轮 | 60 字 | ≈ RSA-15360 | 最慢 |
我在项目里见过不少团队无脑上AES-256,觉得越长越安全。其实如果你只是在内部系统加密配置或日志,AES-128完全够用。只有面对国家级对抗或合规强制要求(比如某些金融/政务场景)才必须上256。差别也不小——256比128大约慢40%,移动端上尤其明显。
3.2 分组模式:ECB / CBC / CFB / OFB / CTR 怎么选?
模式决定了每个分组怎么独立、怎么关联。下面这张图可以帮助你快速理解它们的区别:
看完图再逐个说。
ECB(电子密码本模式)
特点:每个分组独立加密,相同的明文块产生相同的密文块。
我踩过的坑:曾经有个同事用ECB加密用户头像,结果头像轮廓完全能被看出来。相当于没加密。ECB只适合随机密钥加密信息(比如密钥包装),其他场景千万别碰。
CBC(密文分组链接模式)
每个分组先和上一个密文块异或,再加密。第一个分组需要初始化向量(IV)。
优点:相同的明文块产生不同的密文块,安全性高。错误会传播两段,但也因此具有认证特性。
实际推荐:我个人做文件加密首选CBC,搭配随机IV。但注意:IV必须随机,且解密端必须拿到IV。
CFB(密文反馈模式)
实际上是把AES当成流密码来用。加密时输出密钥流与明文异或。也需IV。
优点:不需要填充,适合流式数据。
缺点:每次加密一个字节(或段),速度慢,错误会无限传播。
OFB(输出反馈模式)
类似CFB,但密钥流生成是脱密的:加密输出反馈到下一次输入,不与密文相关。
优点:误差不传播。
注意:IV不能重复——如果相同的IV用了两次,密钥流就完全一样,密文就能被破解。
CTR(计数器模式)
对计数器+IV进行加密,再与明文异或。计数器依次递增。
优点:天然并行,可预计算,不需要填充。
实际使用:我现在做流量加密、视频流加密基本都用CTR。不过一定得保证每个分组都用不同的计数器值(包括高位重叠)。
3.3 初始化向量(IV)的讲究
IV在很多模式里是必需的。它负责打破相同明文产生相同密文的规律。几个关键点:
- 长度固定:AES的IV总是16字节(128位),不能多不能少。
- 随机性:必须用密码学安全的随机数生成器(比如Python的os.urandom),别用时间戳或自增ID。
- 不保密:IV会随密文一起传输,明文存储即可。但是不能被篡改——建议加上MAC验证。
- 不可重复使用:相同密钥下IV不能重复,否则CTR/OFB会泄露密钥流,CBC也会暴露明文块之间的关系。
3.4 填充(Padding)
AES分组加密要求明文长度是16的倍数。如果不满,就要填充。常见的填充方案:
| 填充方式 | 规则 | 使用场景 |
|---|---|---|
| PKCS7 | 缺几个字节就填数字几,比如缺5字节就填5个0x05 | 最通用,几乎所有库默认 |
| ISO 10126 | 最后一位填缺的字节数,前面填随机字节 | 曾经流行,现已废弃 |
| ANSI X.923 | 除了最后一位填缺的字节数,其余填0 | 少见 |
| Zeros(补0) | 仅适用于二进制数据,文本不可靠 | 特殊协议,非标准 |
实际项目中99%都用PKCS7。我建议你也用这个,几乎所有语言/库都支持。代码里对齐好就行:
# Python示例:用pycryptodome做AES-CBC加密
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os
key = os.urandom(32) # AES-256
iv = os.urandom(16) # 随机IV
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = b"Hello, AES!"
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
# 解密
cipher2 = AES.new(key, AES.MODE_CBC, iv)
decrypted = unpad(cipher2.decrypt(ciphertext), AES.block_size)
print(decrypted) # b'Hello, AES!'
注意:pad 和 unpad 必须使用同一个填充标准(PKCS7 即默认)。如果你在跨平台对接,一定要确认双方用同一套。
3.5 参数归纳总结
最后一张表格帮你快速对照:
| 参数 | 关键点 | 我推荐 |
|---|---|---|
| 密钥长度 | 128/192/256,轮数不同 | 内部用128,合规用256 |
| 分组模式 | ECB不推荐,CBC最常用,CTR适合流 | 文件用CBC,流用CTR |
| IV | 16字节,每次随机,不重复 | os.urandom(16) |
| 填充 | PKCS7默认,CFB/OFB/CTR不用填充 | 始终用pad/unpad |
嗯,参数就是这些。记住一个原则:加密不是“加密一次就能用”,而是一整套参数约定的工程。 把表格里的关键点写进你的开发规范,能省大量排查时间。