4. Associated Data处理
讲CCM,绕不开Associated Data,简称AD。
很多人觉得AD就是“附带数据”,随便传一下就行。我见过不少项目,加密解密都对了,偏偏AD处理出了岔子,结果认证死活过不去。说白了,AD是CCM安全性的基石之一,处理不好,整个认证加密就形同虚设。
4.1 什么是Associated Data?
AD,全称是Associated Data,中文叫“关联数据”。它的核心特点是:参与认证,但不参与加密。
你想想看,有些数据需要保证完整性,但不需要保密。比如网络包头的某些字段、文件元数据、协议版本号。这些信息如果被篡改,接收方必须能发现,但内容本身没必要加密——因为本来就是公开的。
AD的三大特性:
- 认证保护:AD会被纳入MAC计算,任何篡改都会被检测到
- 不加密:AD以明文形式传输,不进行加密处理
- 长度灵活:可以为0,也可以长达2^64 - 1字节
我个人习惯把AD理解成“防伪标签”。它不藏起来,但谁要是动了它,一眼就能看出来。
4.2 AD的编码规则
AD的编码,在NIST SP 800-38C里有明确规定。我当年第一次看规范时,觉得这规则有点绕。后来踩过坑才明白,每一步都有它的道理。
编码规则分两种情况:
4.2.1 当AD长度为0时
最简单的情况。什么都不用编码,直接跳过。B0块之后就是加密数据。
4.2.2 当AD长度大于0时
这时候需要按照以下规则编码:
- 判断长度范围:
- 如果AD长度在0到2^16 - 2^8之间(即0到65280字节):用2字节表示长度
- 如果AD长度在2^16 - 2^8到2^32之间:用6字节表示长度,前2字节固定为0xFFFE
- 如果AD长度在2^32到2^64 - 1之间:用10字节表示长度,前2字节固定为0xFFFF
- 编码格式:长度字段 + AD原始数据
- 填充要求:整个AD编码块必须按16字节对齐,不足部分补0
我的经验:大多数实际场景中,AD长度都在65280字节以内。所以最常见的编码就是2字节长度 + AD数据 + 可能的填充。别一上来就写复杂的长度判断逻辑,先确认你的AD长度范围。
来看一个具体的编码示例:
# 假设AD = b"Hello, CCM!",长度13字节
# 13 < 65280,所以用2字节表示长度
ad = b"Hello, CCM!"
ad_len = len(ad) # 13
# 编码:2字节大端长度 + AD数据
encoded_ad = struct.pack(">H", ad_len) + ad # 共15字节
# 填充到16字节倍数
padding_len = 16 - (len(encoded_ad) % 16)
if padding_len != 16:
encoded_ad += b'\x00' * padding_len
# 最终encoded_ad长度为16字节
print(encoded_ad.hex())
# 输出:000d48656c6c6f2c2043434d2100000000
注意看:000d就是长度13,后面跟着AD内容,最后补了1个字节的0。
4.3 AD在CCM认证中的位置
AD编码完成后,会被拼接到B0块之后,一起参与CBC-MAC计算。整个认证数据的结构是这样的:
认证输入 = B0块 || 编码后的AD || 密文
B0块是控制信息,AD是关联数据,密文是加密后的载荷。这三部分串起来,一起算MAC。
我画了一张图,帮你理清这个流程:
4.4 常见错误与避坑指南
AD处理看似简单,但坑不少。我这些年做安全审计,发现AD相关的bug占了CCM实现错误的很大比例。
错误一:AD编码时长度字段字节序搞错
规范明确要求使用大端序(Big-Endian)。我见过有人用struct.pack("<H", ad_len),结果长度字段反了,认证死活过不去。
避坑指南:编码AD长度时,务必使用大端序。Python里用struct.pack(">H", ...)或int.to_bytes(length, 2, 'big')。
错误二:AD填充逻辑写错
填充只针对编码后的AD块,不是原始AD数据。我曾经见过一个实现,把原始AD填充到16字节,然后才加长度字段——顺序完全反了。
# 错误写法
padded_ad = ad + b'\x00' * (16 - len(ad) % 16) # 先填充原始AD
encoded = struct.pack(">H", len(ad)) + padded_ad # 再加长度
# 正确写法
encoded = struct.pack(">H", len(ad)) + ad # 先加长度
padding = 16 - len(encoded) % 16
if padding != 16:
encoded += b'\x00' * padding # 再填充编码后的整体
错误三:AD长度为0时还编码
当AD长度为0时,规范要求不编码任何内容。有人习惯性地加了个空长度字段,结果多出来2个字节,破坏了MAC计算。
我的建议:写代码时,先判断if ad:,只有非空才走编码逻辑。空AD直接跳过,别画蛇添足。
错误四:接收方AD验证不一致
这是最隐蔽的坑。发送方和接收方对AD的理解必须完全一致。我遇到过这样一个案例:发送方把协议版本号编入AD,接收方却把版本号+保留字段一起编入AD。两边AD不一样,MAC验证永远失败。
避坑指南:AD的构成必须在通信双方之间严格约定。任何一方多一个字节、少一个字节,或者顺序不同,都会导致认证失败。建议在协议文档里明确写出AD的组成规则,并用测试向量验证。
4.5 AD处理的最佳实践
说了这么多坑,我总结几条实用的建议:
| 实践要点 | 具体做法 | 原因 |
|---|---|---|
| 统一编码函数 | 发送方和接收方共用同一个AD编码函数 | 避免两边实现不一致 |
| 测试向量验证 | 用NIST提供的CCM测试向量验证AD编码 | 确保实现与标准一致 |
| AD长度限制 | 尽量控制在65280字节以内 | 避免复杂的长度编码逻辑 |
| 日志记录 | 调试时打印AD编码后的hex值 | 方便对比发送方和接收方的编码结果 |
| 协议文档化 | 明确写出AD包含哪些字段及其顺序 | 防止理解偏差导致认证失败 |