设备安全评审里经常会听到一句话:“数据用 AES 加密了。”
这句话不够。AES 是一个分组密码,它只定义了怎样用密钥把一个 16 字节分组变成另一个 16 字节分组。真实消息往往不止 16 字节,也可能包含重复内容、协议头、长度、填充和多包传输。
所以落地时真正要问的不是“是不是 AES”,而是:
- 用的是哪种工作模式
- IV 或 nonce 从哪里来
- 长度不是 16 字节整数倍时怎么处理
- 密文能不能被篡改而不被发现
- 解密失败时错误会不会泄露信息
- 同一把 key 下会不会复用计数器或 nonce
一个稳定的第一层模型是:AES 是基础分组变换,工作模式决定怎样把它扩展成可用于长消息的加密方案。mode 决定了重复块是否泄露、是否需要 padding、IV/nonce 有什么要求、有没有认证,以及错误处理会不会变成攻击面。
AES block cipher:
16-byte block + key -> 16-byte block
mode:
many bytes + key + IV/nonce -> ciphertext
maybe also -> authentication tag
只说“用了 AES”,就像只说“用了发动机”,还没说这辆车有没有刹车、方向盘和安全带。
AES 本身只处理一个分组
AES 的分组大小固定是 128 bit,也就是 16 字节。AES-128、AES-192、AES-256 里的 128/192/256 指的是密钥长度,不是分组大小。
最底层可以理解成:
C = AES_Encrypt(K, P)
P = AES_Decrypt(K, C)
其中 P 和 C 都是 16 字节。
但工程里的数据通常是:
- 几十字节的设备状态
- 几百字节的 JSON 或 CBOR
- 固件分片
- 文件内容
- 协议帧和业务 payload
这些数据要么超过 16 字节,要么长度不对齐,要么会在不同消息里出现重复结构。工作模式就是为了解决“怎样把 16 字节分组密码用在真实消息上”。
ECB 泄露重复结构
ECB 是最直观也最危险的模式。
它把明文切成 16 字节一块,每块独立 AES 加密:
P1 -> AES(K) -> C1
P2 -> AES(K) -> C2
P3 -> AES(K) -> C3
P1 == P3 -> C1 == C3
问题是:相同明文块会得到相同密文块。
攻击者即使不知道明文内容,也能看出重复模式。对图片、结构化报文、固定字段、重复命令、设备状态来说,这会泄露很多信息。
ECB 几乎不应该用于普通业务数据加密。它最多出现在非常底层、非常受限、上层已经严格处理分组语义的场景。工程上看到 AES/ECB,默认要提高警惕。
CBC 用 IV 打断重复,但不认证
CBC 会把每个明文块先和前一个密文块异或,再送进 AES。
第一块没有前一个密文,所以使用 IV:
IV
|
v
P1 -> xor -> AES(K) -> C1
|
v
P2 -----------------> xor -> AES(K) -> C2
|
v
P3 --------------------------------> xor -> AES(K) -> C3
这样即使两条消息开头相同,只要 IV 不同,第一块密文也不同,后续链路也会变化。
CBC 的关键要求是:
- IV 通常不需要保密,但必须不可预测或至少按协议要求正确生成
- 同一 key 下不要用固定 IV 加密多条相似消息
- 明文长度不是 16 字节整数倍时,需要 padding
- CBC 本身只加密,不提供完整性认证
最后一点最容易被低估。攻击者不能直接看懂密文,不等于不能改密文。CBC 没有 tag,接收方无法仅靠 CBC 知道密文是否被篡改。
所以 CBC 如果必须使用,通常要配合 MAC,并且要明确顺序和错误处理。现代协议更常见的建议是直接使用成熟 AEAD。
padding 是 CBC 常见攻击面
CBC 需要处理非 16 字节整数倍的明文。常见做法是 PKCS#7 padding。
例如最后还差 5 字节填满一块,就补 5 个 0x05。如果刚好已经对齐,也要补一整块,避免解密时无法判断最后几个字节是不是填充。
问题在于,解密时接收方要检查 padding 是否正确。
如果系统对不同错误返回不同信息,例如:
- padding 错
- MAC 错
- 格式错
- JSON 解析错
攻击者可能利用这些差异逐步试探明文。这就是 padding oracle 类问题的来源。
更稳的原则是:CBC 方案不要自己拼;如果历史兼容必须用,优先使用经过审计的协议组合,认证要覆盖密文和上下文,错误处理不要向外泄露细节。
CTR 把分组密码变成类流加密
CTR 模式不直接加密明文块。它加密一串计数器,生成密钥流,再和明文异或:
nonce || counter1 -> AES(K) -> S1
P1 --------------------------> xor -> C1
nonce || counter2 -> AES(K) -> S2
P2 --------------------------> xor -> C2
它的好处是:
- 不需要 padding
- 可以并行生成密钥流
- 适合任意长度数据
- 解密和加密结构相同
但 CTR 有一个硬边界:同一 key 下,不能复用同一段 nonce/counter。
如果两条消息用了同一密钥流:
C1 = P1 xor S
C2 = P2 xor S
攻击者可以计算:
C1 xor C2 = P1 xor P2
这会泄露两个明文之间的关系。CTR 自身也不提供认证,所以还要额外 MAC,或者使用基于 CTR 加认证的 AEAD 模式。
GCM 是加密加认证
AES-GCM 是现代协议里非常常见的 AEAD 模式。
它大致由两部分组成:
+-> CTR encryption -> ciphertext -+
key, nonce -+ |
v
AAD ---------------------------------------> GHASH -> tag
GCM 不只是“另一种 AES 加密方式”。它同时提供:
- 明文加密
- 密文完整性认证
- AAD 认证
- tag 验证
AAD 是不加密但要防篡改的上下文,例如协议头、设备 ID、消息类型、序号、版本号。
key + nonce + plaintext + AAD
-> ciphertext + tag
接收方必须验证 tag。tag 失败时,密文、AAD、nonce 或 key 至少有一处不匹配,业务层不应该继续处理明文。
GCM 的核心要求是 nonce 不能在同一 key 下重复。nonce 复用不只是小问题,可能同时破坏保密性和认证安全。更深入的 nonce 持久化、断电回滚和重放问题,应该放到 AEAD nonce 设计里看。
CCM 常见于嵌入式和无线协议
AES-CCM 也是 AEAD。它组合的是:
plaintext -----------------> CTR encryption -----> ciphertext
AAD + plaintext + nonce ---> CBC-MAC ------------> tag
很多嵌入式、低功耗和无线协议会使用 CCM,因为硬件 AES 加速常见,协议也早已规定好参数。
CCM 和 GCM 一样,不应该被理解成“只加密”。它也输出密文和认证 tag,也要求 nonce 在同一 key 下满足唯一性要求。
工程上如果协议已经规定 AES-CCM,例如某些无线或 IoT 协议,就按协议参数做,不要随意改 nonce 长度、tag 长度、AAD 组织方式和计数器范围。
XTS、KW 这类模式不要混用到通信协议
AES 还有一些专用模式,例如:
- XTS:常用于磁盘或块设备加密
- KW / KWP:用于密钥包裹
- CMAC:用于消息认证,不加密
这些模式有自己的使用边界。看到“也是 AES mode”,不等于可以拿来保护设备通信 payload。
通信协议通常关心消息边界、认证、重放、序号、会话密钥和错误处理。磁盘加密或密钥包裹关心的是另一类对象。领域不同,模式不能随便搬。
新设计该怎么选
如果是在设计新的设备通信或应用层协议,优先顺序通常很简单:
第一,优先用成熟协议,例如 TLS、DTLS、Noise、OSCORE 或平台已有安全通道,而不是自己设计加密封装。
第二,如果必须在消息层加密,优先使用 AEAD:
- AES-GCM:通用系统、硬件加速常见
- AES-CCM:嵌入式和无线协议常见
- ChaCha20-Poly1305:没有 AES 加速的软件实现友好
第三,只有为了兼容旧系统才考虑 CBC。此时必须一起检查:
- IV 是否正确生成
- padding 是否安全处理
- MAC 是否存在
- 是 encrypt-then-MAC 还是别的组合
- 错误返回是否会形成 oracle
- 密钥是否按用途分离
第四,ECB 不应用于普通业务数据。
看到一套 AES 方案时先问什么
安全评审或现场排查时,不要停在“是不是 AES”。
可以按这条链看:
AES mode 是什么
-> 是不是 AEAD
-> IV / nonce 是否满足要求
-> 是否需要 padding
-> 是否认证密文和上下文
-> tag / MAC 失败后是否停止处理
-> 同一 key 下计数器是否可能复用
-> 错误返回是否泄露 padding 或解析细节
-> 是否有成熟协议可以替代自定义组合
如果问题表现为“偶发解密失败”“重启后才出错”“服务端 tag 错”“旧设备能通新设备不通”,重点看 mode 参数、IV/nonce、padding、tag 长度和协议版本。
真正要记住的边界
AES 是可靠的基础算法,但“用了 AES”不是完整安全设计。
ECB 会泄露重复结构。CBC 需要 IV 和 padding,而且本身不认证。CTR 不需要 padding,但 nonce/counter 不能复用,也不认证。GCM 和 CCM 是 AEAD,同时提供加密和认证,但对 nonce、tag、AAD 和错误处理有严格要求。
所以工程上更稳的说法不是“用 AES 加密”,而是说清楚:
用哪种 mode
IV / nonce 怎么生成
是否认证
tag / MAC 怎么验证
失败时怎么处理
mode 选错或组合错,问题不一定表现为立刻解密失败,而可能是系统长期看起来正常,安全边界已经失效。