MAVLink 安全通信系列(2):AES-GCM 原理
返回系列总目录。
本篇只处理密码构件本身:AES 如何构造分组置换,GCM 如何把计数器加密与认证 哈希组合成 AEAD,以及哪些使用约束不能被实现细节掩盖。具体 nonce/AAD 如何映射 到 MAVLink 字段、解析器何时拒绝帧,留给第 3 篇。
1. 加密算法地图:一张图看懂基本关系
加密算法不是铁板一块,先按"密钥怎么用"分两大类,再按"效率/用途"细分:
flowchart TD
CRY["密码学原语"] --> SYM["对称加密<br/>(加解密同一把密钥)"]
CRY --> ASYM["非对称加密<br/>(公钥 / 私钥)"]
CRY --> HASH["哈希 / 认证<br/>(无密钥或共享密钥)"]
SYM --> BLOCK["分组密码<br/>AES / SM4 / DES"]
SYM --> STREAM["流密码<br/>ChaCha20 / RC4"]
BLOCK --> MODE["工作模式"]
MODE --> ECB["ECB(不安全)"]
MODE --> CBC["CBC(加密)"]
MODE --> CTR["CTR(流化)"]
MODE --> AEAD["AEAD 认证加密<br/>GCM / CCM"]
ASYM --> ECDH["ECDH(密钥协商)"]
ASYM --> ECDSA["ECDSA(数字签名)"]
ASYM --> RSA["RSA(加解密/签名)"]
HASH --> SHA["SHA-256(完整性)"]
HASH --> HMAC["HMAC(带密钥认证)"]先记住三条结论:
- 对称加密负责"大量数据"的高效加解密;非对称负责"密钥协商 + 身份 签名"这类小而贵的操作;两者在工程里是配合关系。
- 分组密码需要"工作模式"才能加密任意长度数据。其中 GCM / CCM 这类 AEAD 模式能"加密 + 认证一次完成",是今天的主流选择。
- 我们的项目就是典型的配合:ECDHE 协商共享秘密,证书与 ECDSA 认证握手; HKDF 派生密钥,再由 AES-128-GCM(对称 AEAD)保护业务数据。ECDSA 本身不 协商密钥,ECDH 本身也不认证对端。
2. 对称加密:分组密码与流密码
2.1 分组密码
以固定长度块为单位处理数据。AES 块长 128 位(16 字节);不足一整个块的 数据由工作模式处理(不一定使用 padding)。分组密码本身是"确定性置换"——同一把密钥、同一块输入, 永远得到同一块输出,所以不能直接拿它重复加密任意业务数据;必须使用经过分析的 工作模式,并正确管理 nonce/计数器及密钥生命周期。nonce 不一定是随机数,但必须 满足具体模式规定的唯一性或不可预测性要求。
2.2 流密码
把密钥扩展成一条"密钥流",与明文逐字节异或。结构简单、纯软件极快,代表是 ChaCha20(见第 4 节对比)。
2.3 一次看懂工作模式
同一把 AES 在不同模式下行为完全不同:
| 模式 | 加密单元 | 是否认证 | 并行 | 注意点 |
|---|---|---|---|---|
| ECB | 每块独立 | ❌ | ✅ | 相同明文块→相同密文块,泄露结构,禁止使用 |
| CBC | 前块密文链式 | ❌ | ❌ | 需 padding;错误的错误处理可能形成 padding oracle |
| CTR | 计数器生成密钥流 | ❌ | ✅ | 把分组密码"流密码化" |
| GCM | CTR 加密 + GHASH 认证 | ✅ | ✅ | nonce 必须唯一(见 7.2) |
| CCM | CTR 加密 + CBC-MAC 认证 | ✅ | ❌ | 代码比 GCM 小,但两遍处理、更慢 |
3. AEAD:加密与认证的一次性打包
传统做法是"先加密、再套一层 HMAC"(encrypt-then-MAC),两遍处理、两套密钥。 AEAD(Authenticated Encryption with Associated Data)则把二者合并进一次 操作,并且额外支持 AAD(附加认证数据):
flowchart LR
subgraph IN["输入"]
P["明文 Plaintext"]
K["密钥 Key"]
N["nonce(每把密钥下唯一)"]
A["AAD(可选,认证但不加密)"]
end
subgraph AE["AEAD 算法(如 AES-GCM)"]
E["加密"]
T["认证标签生成"]
end
subgraph OUT["输出"]
C["密文 Ciphertext"]
TAG["认证标签 Tag(防篡改)"]
end
P --> E --> C
K --> AE
N --> AE
A --> T --> TAG- nonce:公开的每条加密操作输入;同一把密钥下必须唯一。随机生成时还需评估 碰撞概率与密钥使用量;nonce 不会自动防止重放;
- Tag:认证标签,接收方先验证再使用明文。篡改被认证的密文或 AAD 会导致 验证失败。共享密钥 AEAD 不能在两个密钥持有方之间区分消息来源;
- AAD:只认证不加密的字段。本项目当前只把 MAVLink header 的
incompat_flags、compat_flags、seq、sysid、compid作为 AAD;AAD 不会 增加密文长度,但未纳入的len、msgid仍不受标签保护(见第 3 篇)。
AEAD 是一种接口与安全目标的组合,而不是身份认证或抗重放协议。算法选型还要看 芯片指令集、实现成熟度、吞吐、功耗、nonce 管理、互操作和合规验证状态。本项目 选择 AES-GCM,是工程取舍,并不意味着它是唯一满足这些约束的算法。
4. AES-GCM 拆解(上):AES 是什么、怎么工作
AES-GCM 是"AES(分组密码)+ GCM(认证加密模式)"的组合。要懂 GCM,先懂 AES。
4.1 AES 概要
AES(Advanced Encryption Standard,NIST FIPS 197)是一种对称分组密码:
- 对称:加密与解密用同一把密钥;
- 分组:块长固定 128 位(16 字节);
- 密钥长度:128 / 192 / 256 位三档,对应 10 / 12 / 14 轮;本项目用 AES-128 (16 字节密钥,10 轮)。
4.2 轮结构:SPN 网络
AES 采用 代换-置换网络(SPN):把 16 字节输入排成 4×4 字节矩阵(列优先), 经过多轮重复的四步变换。整体流程:
flowchart TB
IN["16 字节明文"] --> ARK["AddRoundKey(首轮密钥 0)"]
ARK --> LOOP{"重复 9 轮"}
LOOP --> SB["SubBytes 非线性代换"]
SB --> SR["ShiftRows 行移位"]
SR --> MC["MixColumns 列混合"]
MC --> ARK2["AddRoundKey"]
ARK2 --> LOOP
LOOP --> SB2["SubBytes(末轮,省略 MixColumns)"]
SB2 --> SR2["ShiftRows"]
SR2 --> ARK3["AddRoundKey(轮 10)"]
ARK3 --> OUT["16 字节密文"]四步变换各自的作用:
| 变换 | 类型 | 作用 | 关键点 |
|---|---|---|---|
| SubBytes | 代换 | 每字节查 S-box 做非线性替换 | AES 唯一非线性环节,提供"混淆" |
| ShiftRows | 置换 | 每行循环左移 0/1/2/3 字节 | 提供"扩散" |
| MixColumns | 线性混合 | 每列做一次 GF(2⁸) 多项式乘 | 继续"扩散"(末轮省略) |
| AddRoundKey | 异或 | 与轮密钥逐字节异或 | 把密钥"编入"数据 |
SubBytes 的 S-box
S-box 是 256 字节的查表,输入字节 b 输出 S[b]。它可分解为两步:先在 GF(2⁸) 上求乘法逆(0 映射为 0),再做一次仿射变换,提供 AES 轮结构中的非线性。 下表是标准 AES S-box 的前 16 个值(本项目 mavlink_aes_gcm.h 里的 _mav_aes_sbox 就是这个表,256 字节,也是纯软件实现里最大的常量表):
S[0..15] = 63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76MixColumns 的数学
MixColumns 把每列当作 GF(2⁸) 上的多项式,乘以固定多项式 a(x) = 03·x³ + 01·x² + 01·x + 02 再对 x⁴+1 取模。写成矩阵(用十六进制系数):
其中 02· 就是 GF(2⁸) 上的"乘二"操作(左移 1 位,若最高位为 1 再异或 0x1B)。
4.3 密钥扩展
主密钥经密钥扩展(Key Expansion)生成全部轮密钥:AES-128 一共 11 组 × 16 字节 = 176 字节。纯软件实现的核心开销就是 256 字节 S-box + 176 字节轮密钥,RAM 占用极小——这正是它适合嵌入式的 原因之一。
5. AES-GCM 拆解(下):GCM 如何把 AES 变成 AEAD
AES 只加密、不认证。GCM(Galois/Counter Mode,NIST SP 800-38D)在 AES 之上 组合两件事——CTR 加密保证机密性 + GHASH 认证保证完整性与真实性:
flowchart LR
subgraph S1["CTR 加密(机密性)"]
CTRK["AES_K(nonce‖counter)"]
end
subgraph S2["GHASH 认证(完整性 / 真实性)"]
GH["GF(2^128) 上的乘法累加"]
end
subgraph S3["结合"]
TAG["Tag = GHASH ⊕ AES_K(J0)"]
end
CTRK --> S3
GH --> S35.1 三个关键量
| 量 | 定义 | 说明 |
|---|---|---|
| H | H = E(K, 0^128) | 哈希子密钥,用全零块加密一次得到 |
| J0 | J0 = nonce(96bit) ‖ 0x00000001 | 初始计数器块(96 位随机 nonce 时) |
| Tag | T = GHASH_H(A,C) ⊕ E(K, J0) | 认证标签,本项目取满 128 位(16 字节) |
5.2 CTR 加密:把 AES 当密钥流
AES-CTR 不做传统的块加密,而是用 nonce + 计数器 反复生成密钥流,跟明文异或:
inc32只递增最低 32 位(NIST 规定),计数器空间受限,因此单条消息长度 也有规范上限;- 加解密完全对称(同一密钥流、都做异或),可并行,且密文长度 = 明文长度;
- nonce 必须唯一:同一密钥下复用会重复 CTR 密钥流,泄露明文关系,并可能 破坏认证安全(见第 7 节)。
5.3 GHASH 认证:GF(2^128) 上的乘法
GHASH 是一个通用哈希:把 AAD 和密文分块,在 GF(2^128) 有限域上做"乘法累加", 把每个字节都"搅"进一个 128 位状态:
其中块序列 B 依次为:
B = [AAD 按 16 字节分块(末块补零)]
‖ [密文 C 按 16 字节分块(末块补零)]
‖ [len(AAD)]_64 ‖ [len(C)]_64 ← 长度块,各自 64 位大端(单位:bit)最后一个长度块是关键安全设计:长度也参与哈希,攻击者无法在 AAD 和密文之间 搬移、拼接数据而不被发现。
GF(2^128) 有限域的定义(NIST GCM):
两个 128 位数的乘法按多项式乘法、再对 f(x) 取模。纯软件可用"右移异或"实现, 约 128 次迭代;本项目的约减常数是 R = 0xE1 << 120(对应 x^7+x^2+x+1 的移位 表示)。这是 GCM 在纯软件上相对 CCM 略贵的部分(需要 GF 乘法),换来的是一遍 处理 + 可并行。
5.4 完整加密 / 解密流程
加密(本系列后面 MAVLink 的打包路径与此一一对应):
flowchart TB
subgraph 加密
H["H = AES_K(0^128)"] --> C1
C1["J0 = nonce ‖ 00000001"]
C1 --> CTR["CTR:C_i = P_i ⊕ AES_K(inc32^i(J0)),i 从 1 开始"]
CTR --> CT["密文 C"]
AAD["AAD(认证数据)"] --> GH["GHASH_H(AAD, C)"]
CT --> GH
J0E["S = AES_K(J0)"] --> XOR["T = GHASH ⊕ S"]
GH --> XOR --> TAG["Tag(16 字节)"]
CTR --> OUT["输出:nonce + C + Tag"]
end解密 / 认证:接收方重算认证标签,并与收到的 Tag 做常量时间比较;不等 即判定验证失败。实现必须确保失败时明文不会被上层读取。许多 API 会先写出候选 明文再报告失败,因此调用方应以验证结果作为唯一的数据可用性门槛。
常量时间比较很关键:普通
memcmp遇到第一个不同的字节就返回,等于泄露了 "前几个字节是对的";常量时间比较从头比到尾,时长与内容无关,杜绝时序侧信道。
6. 其它候选算法横向对比
给 MAVLink 选加密算法,候选路线与取舍(加粗为本项目选型):
| 方案 | 技术栈 | 优点 | 缺点 |
|---|---|---|---|
| 原始 CRC | CRC-16/MCRF4XX | 零开销、官方默认 | 仅检错,不加密、不防篡改 |
| MAVLink v2 签名 | SHA-256 + 共享密钥 | 官方内置、每帧 +13B、防篡改 | 不加密、全网同钥、无前向保密、无身份 |
| AES-CBC + HMAC | encrypt-then-MAC | 经典成熟、老硬件兼容好 | 两遍处理、双密钥、易踩 padding oracle |
| AES-CCM | CTR + CBC-MAC | 同为标准 AEAD,适合受限实现 | MAC/加密的处理顺序可能增加缓冲或遍历成本;nonce 长度配置受约束 |
| AES-GCM | CTR + GHASH | 可并行、平台支持 AES/GHASH 时性能好、标准化 | 需 GF(2^128) 乘法、nonce 必须唯一 |
| ChaCha20-Poly1305 | 流密码 + Poly1305 | 纯软件实现通常性能好 | 平台加速、实现成熟度及合规性需逐目标核实 |
| Ascon-128 | Ascon 置换(轻量级 AEAD) | 为受限设备设计 | 目标平台支持、生态成熟度与合规性需逐项评估 |
| SM4-GCM | SM4 + GHASH(国密) | 国密合规、国产芯片有加速 | 非 NIST、海外互操作弱 |
不能从算法名称直接推出硬件加速、FIPS 合规或安全等级:这些属性取决于具体芯片、 密码模块及其验证状态。CRC 不认证;signing 不加密;CBC-HMAC、CCM、GCM 和 ChaCha20-Poly1305 都有成熟应用,取舍应放在目标平台与威胁模型中比较。本项目 选择 GCM,主要是为了匹配 AES 加速和现有软件栈。
选 AES-GCM 的工程理由:
- 一次处理、双重保证:CTR 加密 + GHASH 认证合并在单次操作里,无需再套签名;
- 可利用平台能力:目标 MCU 若提供 AES/GHASH 加速,可在实测后获得吞吐或功耗收益;
- 标准化:GCM 已由 NIST 标准描述;采用标准算法不等同于产品或密码模块已 获得 FIPS 验证;
- 嵌入式适配:纯软件实现可控制代码和内存规模,但实际开销、恒定时间特性及 抗侧信道能力必须在目标 MCU 上测量和评估;
- 开销可控:nonce(12B) + tag(16B) = 28 字节固定开销,与签名方案(13B)量级 相当,却额外提供机密性。
7. AES-GCM 的优缺点与使用红线
7.1 优点
- AEAD 一体化:加密 + 认证一遍,免去"加密后单套 HMAC"的复杂与开销;
- 加密与加密可并行、密文不膨胀(长度等于明文);
- 有成熟标准与广泛互操作实现;硬件加速与合规状态依具体平台和验证而定;
- 纯软件实现轻,适合 MCU。
7.2 缺点与红线
- nonce 绝不能重用:同一
(key, nonce)用两次,攻击者异或两次密文即可得到 两条明文的异或关系,并可能进一步伪造认证标签。部署必须使用经过审查的熵源/ DRBG,或设计可证明唯一的计数器方案;不能把普通 PRNG 当密码学随机源; - GF 乘法比纯 CTR 略贵:纯软件上比 ChaCha20 慢,但硬件加速可弥补;
- 随机 nonce 的碰撞风险随使用量平方增长:独立均匀抽取 96 位 nonce 时,
q条消息的碰撞概率近似为q(q-1)/2^97(生日界近似)。q=2^32时约为2^-33,并非所有 GCM 用例通用的"安全上限";还必须考虑总处理块数、允许的 认证失败次数和目标风险。应为每个密钥定义明确的数据量/时间上限,并在到限前 重新握手换钥。随机 nonce 的独立性还依赖高质量熵源及正确初始化。
8. 小结与下一站
- AES-GCM = AES(SPN 分组密码) + GCM(CTR 加密 + GHASH 认证);
- 关键公式:
C_i = P_i ⊕ AES_K(J0+i)、T = GHASH_H(AAD,C) ⊕ AES_K(J0),域 多项式x^128+x^7+x^2+x+1; - 它在正确管理 nonce、密钥生命周期和认证失败路径时,提供 payload 机密性与 被认证数据的完整性;是否适合作为实现方案取决于平台与协议边界。
下一篇《AES-GCM 与 MAVLink 集成》 会把上面的算法接进 MAVLink v2 的真实链路:加密帧长什么样、ENCRYPTED 位 怎么打、打包 / 解析状态机怎么走、nonce/tag 放哪一字节、代码怎么用。
延伸阅读与规范
- NIST, FIPS 197: Advanced Encryption Standard (AES);
- NIST, SP 800-38D: Galois/Counter Mode (GCM) and GMAC;
- IETF, RFC 5116: An Interface and Algorithms for Authenticated Encryption。
这些标准定义算法与安全要求;本项目自定义 MAVLink 帧格式的安全性仍取决于其 AAD 覆盖、nonce 生命周期、密钥管理和接收端状态,不能仅因使用标准算法就推定 整体协议符合标准或已通过认证。
