MAVLink 安全通信系列(1):MAVLink 帧与信任边界
返回系列总目录。
本系列来自 nextpilot-secure-mavlink 项目,目标是在飞控(PX4)与地面站(QGC)之间的 MAVLink 链路上,用 AES-GCM 加密 + X.509 双向认证握手,在明确威胁模型与实现边界的前提下增强链路安全。
本文是系列第 1 篇,先把地基讲透:MAVLink 是什么、一帧数据逐字节长什么样、 链路上到底怎么收发、以及它有哪些与生俱来的安全问题。理解了这些,才能明白 后面的加密与握手究竟在补哪块短板。
0. 问题先行:MAVLink 能传消息,但不等于安全信道
MAVLink 的核心任务是以较低开销编码、校验和路由飞控消息。安全通信还要回答另外 几件事:链路旁观者能否读到遥测?接收端能否识别被修改的命令?它如何判断对端持有 哪个设备的凭证?双方如何建立并更新会话密钥?
不能把这些问题简化成“MAVLink 完全没有安全机制”:MAVLink 2 提供可选 signing, 可以在共享密钥和正确接收端状态的条件下验证帧并限制部分重放。但 signing 不加密 payload,也不提供公钥证书身份或会话密钥协商。CRC 则服务于传输检错,不是对抗 主动攻击者的认证机制。因此,若应用要求机密性、设备身份认证和会话密钥管理, 仅启用原生帧格式及 signing 仍不足以构成所需的安全信道。
本系列的出发点不是“为了加密而改协议”,而是先从这些需求推出改造范围:在保留 MAVLink 消息路由与既有明文互操作能力的前提下,为 v2 加入可识别的加密帧格式、 收发端认证处理路径,并另行设计握手与密钥注入。下文先拆解原始帧和现有机制,再 说明哪些协议位置必须扩展、扩展后仍有哪些安全边界。
1. MAVLink 是什么
MAVLink(Micro Air Vehicle Link)是无人机生态里事实上的标准通信协议,由 Lorenz Meier 于 2008 年发起,如今被 PX4、ArduPilot、QGroundControl(QGC)、 Mission Planner 等主流飞控与地面站广泛采用。
MAVLink 是飞控与地面站(以及机载外设之间)互相交换消息的一套 二进制报文格式。心跳、姿态、GPS、遥控输入、航点 / 任务、命令执行、日志、 相机控制……所有这些都抽象成一条条带编号的消息。整个生态如下:
flowchart LR
subgraph UAV["无人机(UAV)"]
FC["飞控 PX4 / ArduPilot"]
end
subgraph GCS["地面站(GCS)"]
QGC["QGroundControl"]
MP["Mission Planner"]
end
subgraph LINK["物理链路"]
UART["串口 / UART"]
RF["数传 / 射频"]
end
FC -- "MAVLink 二进制报文" --> UART --> QGC
QGC -- "命令 / 心跳 / 任务" --> RF --> FC
MP -.-> QGC
linkStyle default stroke:#666,stroke-width:2px它有几个鲜明特点,也直接决定了它在安全上的定位:
- 面向小带宽链路:专为串口 / UART / 数传这类低带宽、高延迟链路设计,报文 紧凑、开销小。
- 二进制 + 固定偏移:不是 JSON / XML 那种自描述文本,而是一个字节一个 字节排出来的结构体,解析快、内存占用少。
- 消息由 XML 定义、代码自动生成:官方用 XML 描述所有消息,再由
pymavlink生成各语言(C / C++ / Python 等)的序列化 / 反序列化代码。 - 重点是消息传输与互操作:CRC 用于检错;MAVLink 2 signing 是可选的共享密钥 帧认证机制。协议本身不提供 payload 加密、公钥设备身份或会话密钥协商,这些是 本系列要分析和补充的能力。
2. MAVLink 的两个版本
MAVLink 有 v1 和 v2 两个大版本,差异理解清楚很重要(后面会看到,加密只能落在 v2 上):
| 特性 | MAVLink v1 | MAVLink v2 |
|---|---|---|
| 起始标记 magic | 0xFE | 0xFD |
| payload 长度 | 1 字节(最大 255 B) | 同左,仍 1 字节 |
| msgid 宽度 | 1 字节(最多 256 种消息) | 3 字节(最多 2²⁴ 种消息) |
incompat_flags | ❌ 无 | ✅ 有(关键扩展点) |
| 签名(signing) | ❌ 不支持 | ✅ 支持(可选 13 字节) |
| 向后兼容 | — | ✅ 兼容 v1 |
对本文主题最关键的一条:v1 没有 incompat_flags 字段,所以协议层无法用 标志位声明"这是一帧加密帧";加密只能做到 v2 上。这也是官方签名与本文加密方案 都只支持 v2 的根本原因。
3. MAVLink v2 帧结构(逐字节拆解)
一帧 MAVLink v2 报文,从字节 0 到字节 N:
┌────────┬──────────────┬──────────┬───────────┐
│ header │ payload │ checksum │ signature │
│ (10B) │ (0~255 B) │ (2B) │ (可选 13B)│
└────────┴──────────────┴──────────┴───────────┘把 header 展开,逐字节标注:
偏移 大小 字段 说明
0 1 magic 起始标记,v2 固定 0xFD
1 1 len payload 字节数(0~255)
2 1 incompat_flags 兼容性标志位(v2 独有,扩展点)
3 1 compat_flags 兼容性标志位(当前恒 0)
4 1 seq 包序号,每发一帧 +1,收端用于检测丢包
5 1 sysid 系统 ID(区分"哪架飞机 / 哪个地面站")
6 1 compid 组件 ID(同一系统里的飞控、相机、云台…)
7 3 msgid 消息 ID(小端,如 HEARTBEAT=0)
10 N payload 消息体(按 msgid 对应结构排布)
10+N 2 checksum CRC-16
12+N 13 signature 可选,MAVLink v2 签名几个要点:
- header 固定 10 字节:
magic(1)+len(1)+incompat_flags(1)+compat_flags(1) +seq(1)+sysid(1)+compid(1)+msgid(3) = 10。 incompat_flags是关键扩展点:位 0 置位表示带签名(MAVLINK_IFLAG_SIGNED)。 本系列会在位 1 追加一个ENCRYPTED标志声明加密帧。- CRC 的范围:采用 CRC-16/MCRF4XX,计算
header[1..9] + payload + crc_extra。 注意①不含 magic 字节;②每个消息还有一字节crc_extra(从消息 XML 生成, 用于不同消息之间的错误隔离)。
3.1 一条 HEARTBEAT 的真实字节流
以最常见的 HEARTBEAT(msgid=0)为例,看 mavlink_msg_heartbeat_pack() 序列化出来的样子(示意,payload 内部字段已简化):
FD 09 00 00 00 01 01 00 00 00 │ 03 01 04 00 00 00 00 00 00 │ XX XX
└────── header (10B) ────────┘ └────── payload (9B) ───────┘ └ CRC ┘
magic=FD, len=9, msgid=0=HEARTBEAT, sysid=1, compid=1(autopilot)payload 内是 type / autopilot / base_mode / custom_mode / system_status 等 字段按小端排布。只要知道 msgid,就能按对应结构体逐字段解出。
4. 收发路径:解析器是安全机制的接入点
MAVLink 消息由 XML 定义并通过 pymavlink 生成 C/C++ 等语言绑定。应用调用 mavlink_msg_<name>_pack() 生成消息结构,再将其序列化成字节流;接收端逐字节 驱动 mavlink_parse_char(),完成帧边界识别和 CRC 检查后,才按 msgid 解码。 本篇关注这个顺序,而非通用 API 用法:
sequenceDiagram
participant App as 应用
participant Pack as 消息打包/序列化
participant Link as 串口/数传
participant Parse as 逐字节解析器
participant Decode as 消息解码/分发
App->>Pack: 高层消息字段
Pack->>Link: header | payload | CRC | 可选 signature
Link->>Parse: 字节流
Parse->>Parse: 找帧边界、校验长度与 CRC
Parse->>Decode: 完整消息结构
Decode->>App: 按 msgid 分发CRC 通过只是解析器确认帧结构与传输检错条件满足;它不是授权决策。安全扩展通常 需要在消息分发前验证密码学标签,并明确认证失败时如何丢弃消息以及如何避免 上层接触未认证明文。项目加密帧的具体解析钩子、尾部字段和失败路径见 第 3 篇。
5. 官方已有的安全补丁:MAVLink v2 签名(signing)
MAVLink v2 自带一个可选安全机制叫签名:给每帧追加 13 字节,用预共享密钥对 header + payload 计算 SHA-256、取前 48 位作为签名,帧尾附上 link_id + timestamp + 签名:
header │ payload │ CRC │ link_id(1) │ timestamp(6) │ signature(6)
↑ SHA-256 截断到 48 位它提供基于共享密钥的消息认证,并携带链路标识和时间戳;启用接收端时间戳状态 检查时可降低旧帧重放风险。但这不是公钥数字签名:共享密钥的任一持有者都能 生成有效签名,因而无法据此区分具体发送方或提供不可否认性。它也不加密, 飞行数据仍是明文;密钥配置与分发仍由部署方负责。
5.1 校验码、消息认证与加密不是一回事
CRC 的目标是发现噪声或传输损坏,不是对抗有能力重算 CRC 的主动攻击者; MAVLink signing 与 AEAD 标签使用密钥,能抵抗不知道密钥的伪造者;加密则隐藏 被加密的数据内容。三者解决的问题不同,不能用"校验通过"概括成"安全"。
本系列的 GCM 帧也不是整条链路的隐私隧道:header 仍可见,观测者可获知消息 类型、系统/组件标识、长度和时序等元数据。威胁模型应把这些侧信道与 payload 机密性分开讨论。
6. 安全需求与现有机制的边界
从安全工程角度看,MAVLink 是消息传输协议,而不是完整的安全信道协议。差距不在 于它“设计失败”,而在于安全通信还要求传输格式之外的机密性、对端身份和会话密钥 生命周期。对需要抵御链路窃听或主动篡改的部署,至少要分别评估以下需求:
flowchart TD
subgraph A["① 窃听 缺少机密性"]
A1["明文串口 / 数传"]
A2["位置 / 姿态 / 航点全暴露"]
end
subgraph B["② 篡改 / 伪造 认证与身份边界"]
B1["篡改指令 / 注入假消息"]
B2["冒充地面站下发命令"]
end
subgraph C["③ 会话密钥管理 协议层未定义"]
C1["需要额外配置共享密钥"]
C2["缺少标准会话协商与轮换流程"]
C3["部署策略决定泄露影响范围"]
end
A -- "CRC 只检错不防护" --> B --> C6.1 窃听(无机密性)
在未额外部署链路加密的情况下,串口 / 数传上的 MAVLink payload 可被观察者解析, 从而暴露位置、姿态、航点、任务、GPS 坐标等飞行数据。即使启用 signing, 消息仍然是明文。帧头中的 msgid、sysid、compid、长度和时序也会暴露流量 元数据;只加密 payload 并不会隐藏这些信息。
6.2 篡改 / 伪造(完整性、来源认证与身份的边界)
CRC 只校验传输是否发生意外损坏,攻击者修改报文后也可以重算 CRC。MAVLink 2 signing 能补上有密钥的帧认证:攻击者不知道密钥时,接收端可检测伪造和修改;时间戳 状态检查还可限制部分旧帧重放。但 signing 使用共享密钥,因此验证结果只说明报文 来自某个密钥持有者集合,不能在多个持有者之间区分设备身份,也不能替代设备授权、 加密或会话密钥协商。是否启用、如何配置和维护接收端状态,也属于部署需要落实的 条件。
6.3 密钥管理(需要协议外的密钥生命周期)
MAVLink 消息格式没有定义通用的证书信任、会话密钥协商和更新协议。应用若自行 使用预共享密钥保护链路,必须额外设计密钥注入、轮换与泄露处置;如果所有设备 共享一把固定密钥,则:
- 单台设备泄露 → 全网密钥一起泄露;
- 换密钥要重新刷固件 → 机群规模一大根本无法轮换。
背后其实是三个彼此独立的需求缺口:
| 目标 | 对应能力 | MAVLink 现状 |
|---|---|---|
| 机密性 | 加密 | ❌ 明文 |
| 完整性 + 来源认证 | keyed frame authentication | ⚠️ v2 signing 可选;安全性依赖共享密钥和接收端状态 |
| 设备身份与授权 | 证书/身份策略 + 授权绑定 | ❌ 消息格式不提供公钥身份语义 |
| 会话密钥管理 | 安全协商、更新与撤销 | ❌ 没有通用的协议层机制 |
7. 为什么需要扩展 MAVLink:从安全需求到协议改造
因此需要扩展的不只是“加密算法调用”,而是帧格式、解析路径和密钥建立流程。本 项目以 MAVLink v2 为基础,按三层展开:
flowchart TB
subgraph L1["第 1 层 · 加密(机密性)"]
AES["AES-128-GCM 认证加密<br/>payload 加密 + 标签认证"]
end
subgraph L2["第 2 层 · 身份与密钥(真实性 + 分发)"]
X509["X.509 证书双向认证"]
ECDHE["P-256 ECDHE 协商会话密钥"]
X509 -.-> ECDHE
end
subgraph L3["第 3 层 · 工程化(落地)"]
PKI["CA 生成 + 设备发证"]
INT["PX4 / QGC 接入"]
end
AES --> X509 --> PKI --> INT- 扩展帧与收发路径:使用 v2
incompat_flags标识项目自定义的加密帧,在 payload 之外携带 nonce/tag,并修改打包与解析流程——见第 3 篇。该格式不是 MAVLink 官方标准;不认识此标志的实现应拒绝该帧。当前标签只覆盖密文及明确 纳入 AAD 的字段,不应假设所有明文头字段都已受保护。 - 身份识别 + 握手(真实性 + 密钥分发):链路建立后,双方先互验由同一 私有 CA 签发的 X.509 证书,再用 P-256 ECDHE 协商会话秘密,经 HKDF 派生后"热替换"进加密状态——见第 5 篇。PFS 依赖安全随机数与临时密钥清除; 本实现不含 CRL/OCSP,证书身份也未绑定到
sysid。 - 工程集成:为 PX4/QGC 接入新增的收发处理、凭证注入与握手驱动,并检查 帧长、旧实现互操作和失败时的拒绝行为——见第 6 篇。
这类修改带来新的协议兼容面:帧标志、尾部布局、AAD 定义和最大帧长必须在两端 一致;部署还必须决定明文帧是否允许、如何阻止降级,以及认证失败时是否 fail closed。本项目实现提供了研究与验证基础,但仍有头部认证不完整、业务帧缺少 anti-replay、双向复用 traffic key 等已知限制,不能将“改了 MAVLink”或“握手成功” 等同于“链路已完整安全”。
下一篇《AES-GCM 原理》从分组密码、AEAD 到 GCM 的认证机制展开,建立理解后续帧集成的密码学基础。
延伸阅读
- MAVLink Serialization:帧布局、 字段顺序与 CRC 规则;
- MAVLink Message Signing:v2 签名格式、时间戳与接收端状态。
本文的扩展加密帧是项目自定义实现,不属于 MAVLink 官方标准帧格式。
