CCM 核心参数:Nonce、Associated Data、Plaintext、Tag 长度
好,咱们直接进入正题。AES-CCM 这个模式,说白了就是 CTR 模式加密 + CBC-MAC 认证的组合体。你想想看,一个算法要同时干两件事,参数自然就多。我见过不少新手,上来就调库,结果 Nonce 长度搞错,Tag 截断太短,最后上线被攻击者一把梭穿。嗯,这章咱们就把这四个核心参数掰开揉碎讲清楚。
2.1 Nonce:一次性数字的生死线
Nonce 在 CCM 里是绝对的核心。它必须保证:在同一个密钥下,永远不重复。一旦重复,CTR 模式的加密流就会被还原,密文直接裸奔。我在项目中遇到过一起事故,就是因为 Nonce 生成器在进程重启后计数器归零,导致两个会话用了同一个 Nonce,还好是内部测试环境发现的。
CCM 标准(RFC 3610)规定 Nonce 长度可以是 7、8、9、10、11、12、13 字节。为什么是这些值?因为 CCM 的格式字段(Flags)只有 1 个字节,其中 3 位用来表示 Nonce 长度。公式是:NonceLen = 15 - L,其中 L 是消息长度字段的字节数(后面会讲)。
| Nonce 长度(字节) | L 值(消息长度字段字节数) | 最大消息长度(字节) | 常见场景 |
|---|---|---|---|
| 7 | 8 | 2^64 - 1 | 极少用,消息极大 |
| 11 | 4 | 2^32 - 1 | 物联网、传感器数据 |
| 12 | 3 | 2^24 - 1 | Wi-Fi 加密(CCMP) |
| 13 | 2 | 2^16 - 1 | 短消息、蓝牙 |
我个人习惯用 12 字节 Nonce。为什么?因为 Wi-Fi 的 CCMP 协议就用这个长度,生态成熟,库支持好。而且 12 字节足够容纳 64 位时间戳 + 32 位计数器,基本不会撞车。
2.2 Associated Data:认证但不加密的数据
Associated Data(AD),也叫附加认证数据。它的特点是:参与 MAC 计算,但不参与加密。说白了,就是你想确保某段数据没有被篡改,但又不想把它藏起来。
典型场景是什么?网络协议头。比如 IP 包的目标地址、端口号。这些字段接收方必须明文读取才能路由,但你又不希望中间人偷偷改掉。AD 就是干这个的。
我做过一个车联网项目,车辆上报 GPS 数据时,把车辆 ID 放在 AD 里,位置数据放在 Plaintext 里加密。这样网关收到后,先看车辆 ID 是否在白名单里,再解密位置。如果攻击者篡改车辆 ID,MAC 校验直接失败,数据丢弃。
2.3 Plaintext:要加密的明文
Plaintext 就是你想保护的核心数据。CCM 对明文长度有限制,这个限制取决于你选的 Nonce 长度。还记得前面说的 L 值吗?L 越大,能加密的消息越长。
公式很简单:MaxPlaintextLen = 2^(8*L) - 1。举个例子:
- L = 2(Nonce = 13 字节):最大明文 65535 字节
- L = 4(Nonce = 11 字节):最大明文 4GB 左右
- L = 8(Nonce = 7 字节):最大明文 2^64 字节,基本不限
实际项目中,我很少见到需要加密超过 64KB 的场景。大部分物联网报文都在 1KB 以内。所以 L = 2 或 L = 3 就够用了。别为了追求大消息而把 Nonce 缩得太短,Nonce 的唯一性比消息长度重要得多。
2.4 Tag 长度:安全与效率的权衡
Tag 就是认证标签,也叫 MAC 值。它的作用是验证密文和 AD 的完整性。Tag 越长,安全性越高,但网络开销也越大。
CCM 标准支持 Tag 长度为 4、6、8、10、12、14、16 字节。为什么都是偶数?因为 CCM 的格式字段设计如此,奇数长度不支持。
| Tag 长度(字节) | 安全等级 | 推荐场景 |
|---|---|---|
| 4 | 极低 | 几乎不用,碰撞概率太高 |
| 8 | 中等 | 内部网络、低安全要求 |
| 12 | 高 | 大多数商业应用 |
| 16 | 最高 | 金融、政府、军事 |
我个人习惯用 12 字节 Tag。为什么不是 16?因为 12 字节已经提供了 96 位的安全强度,暴力破解需要 2^96 次尝试,这在可预见的未来是安全的。而且 12 字节比 16 字节少 4 字节,对于物联网这种带宽敏感的场景,能省一点是一点。
2.5 参数组合的黄金法则
这四个参数不是孤立的,它们通过 CCM 的格式字节(Flags)紧密耦合。格式字节的结构如下:
Flags = (AdataFlag << 6) | ((M-2) << 3) | (L-1)
其中:
- AdataFlag: 1 表示有 AD,0 表示无 AD
- M: Tag 长度(字节),取值范围 4, 6, 8, 10, 12, 14, 16
- L: 消息长度字段字节数,取值范围 2, 3, 4, 5, 6, 7, 8
举个例子,如果你用 12 字节 Tag(M=12)、2 字节长度字段(L=2)、并且有 AD,那么 Flags 字节就是:
Flags = (1 << 6) | ((12-2) << 3) | (2-1)
= 0x40 | 0x50 | 0x01
= 0x51
这个 Flags 字节会被拼在 Nonce 前面,一起参与 CBC-MAC 计算。所以,发送方和接收方的参数必须完全一致,否则 MAC 校验必败。
2.6 参数选择流程图
下面这张图帮你快速决策:
2.7 实战代码示例
最后,给一个 Python 的 CCM 参数配置示例,用的是 cryptography 库:
from cryptography.hazmat.primitives.ciphers.aead import AESCCM
# 参数配置
key = b'\x01\x02\x03\x04' * 4 # 16 字节 AES-128 密钥
nonce = b'\x00' * 12 # 12 字节 Nonce
data = b'Hello, CCM!' # 明文
aad = b'\x01\x02' # 附加认证数据
# 创建 CCM 对象,指定 Tag 长度为 12 字节
ccm = AESCCM(key, tag_length=12)
# 加密并生成密文 + Tag
ciphertext = ccm.encrypt(nonce, data, aad)
# 解密并验证
decrypted = ccm.decrypt(nonce, ciphertext, aad)
print(decrypted) # 输出: b'Hello, CCM!'
注意看,AESCCM(key, tag_length=12) 这里指定了 Tag 长度。Nonce 长度由你传入的 nonce 字节数决定。库会自动计算 L 值。如果你传了 12 字节 Nonce,L 就是 3,最大明文 16MB。够用了吧?
decrypt() 会抛出 InvalidTag 异常。不要自己手动校验 Tag,让库去做。我见过有人自己写 Tag 比较逻辑,结果用了不安全的字符串比较,被时序攻击破解了。