第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%,移动端上尤其明显。

我的习惯:存用户敏感信息用AES-256,非敏感内部数据用AES-128。密钥长度定了之后,千万别改——否则已加密数据全得重来一次。

3.2 分组模式:ECB / CBC / CFB / OFB / CTR 怎么选?

模式决定了每个分组怎么独立、怎么关联。下面这张图可以帮助你快速理解它们的区别:

AES 分组模式决策与对比 明文分组 (128bit) ECB(电子密码本) CBC(密文分组链接) CFB(密文反馈) OFB(输出反馈) CTR(计数器) ⚠ 相同明文输出相同密文 不推荐用于任何正常加密 需 IV,错误会传播 最通用的块加密模式 流模式,无需填充 适合流式/实时加密 密钥长度 + IV 决定唯一性,模式决定传播与并行特性 最终输出:密文(128bit/块)

看完图再逐个说。

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也会暴露明文块之间的关系。
我曾经犯过的错:在CBC模式下,我用了一个固定的IV向量来简化开发。几个月后发现明文前16字节能被预测攻击。从那以后我再也不偷懒了,每个加密都生成新IV。

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 即默认)。如果你在跨平台对接,一定要确认双方用同一套。

调试口诀:密钥长度对齐、模式一致(ECB不需要IV,其他都要)、IV随机且不重用、填充用PKCS7。这四个对上了,加解密基本不会出错。

3.5 参数归纳总结

最后一张表格帮你快速对照:

参数 关键点 我推荐
密钥长度 128/192/256,轮数不同 内部用128,合规用256
分组模式 ECB不推荐,CBC最常用,CTR适合流 文件用CBC,流用CTR
IV 16字节,每次随机,不重复 os.urandom(16)
填充 PKCS7默认,CFB/OFB/CTR不用填充 始终用pad/unpad

嗯,参数就是这些。记住一个原则:加密不是“加密一次就能用”,而是一整套参数约定的工程。 把表格里的关键点写进你的开发规范,能省大量排查时间。