3. 消息签名与安全机制:签名原理、时间戳、密钥管理、防止重放攻击

聊到 MAVLink 的安全机制,我得先坦白一件事。早些年我做无人机巡检项目时,觉得通信安全是「大厂才需要考虑的事」。结果有一次在演示现场,隔壁团队用一台笔记本加一个 SDR 设备,就把我们无人机的位置、航点和传感器数据全抓出来了。虽然没造成实质损失,但那个画面让我印象很深——从那以后,我再也不敢轻视消息签名了。

MAVLink 2 引入的签名机制,说白了就是给每条消息加了一把「数字锁」。你想想看,无人机在天上飞,地面站发一条「紧急降落」指令,如果这条指令能被伪造或篡改,后果不堪设想。签名机制就是为了解决这个问题。

3.1 签名原理:消息的「身份证」

MAVLink 的签名基于 HMAC-SHA256。它不是对整个消息做加密,而是对消息的特定部分计算一个 6 字节的签名值,附加在消息末尾。

签名的计算范围包括:

  • 消息头(包含长度、序列号、系统 ID、组件 ID、消息 ID)
  • 消息载荷(即实际数据)
  • 时间戳(8 字节,UNIX 微秒时间)
  • 密钥 ID(1 字节,标识使用哪一组密钥)

计算过程大致是这样:

// 伪代码示意
signature = HMAC-SHA256(
    key = secret_key[link_id],
    data = header + payload + timestamp + secret_key[link_id]
)
// 取前 6 字节作为签名
final_signature = signature[0:6]

嗯,这里要注意:MAVLink 只取 HMAC 输出的前 6 字节。为什么是 6 字节?我个人的理解是,在安全性和带宽开销之间取了个平衡。6 字节意味着 2^48 种可能性,对于大多数无人机应用场景来说,暴力破解的代价已经足够高了。

核心要点:MAVLink 签名不是加密,而是认证。它保证消息的完整性和来源可信,但不保护消息内容的机密性。如果你需要加密载荷,得在应用层自己处理。

3.2 时间戳:防重放攻击的第一道防线

重放攻击是什么?简单说,就是攻击者截获一条合法消息,然后过一会儿再重新发送一遍。比如截获一条「解锁电机」的指令,等你降落之后再重放这条消息,无人机可能就会在空中突然解锁。

MAVLink 用时间戳来防止这种攻击。每条签名消息都带一个 8 字节的 UNIX 微秒时间戳。接收方会检查:

  1. 时间戳必须单调递增:新消息的时间戳必须大于之前收到的同源消息的时间戳。
  2. 时间戳不能偏差太大:与本地时间的差值不能超过预设的窗口(通常是 1 秒)。

我曾经在一个项目中遇到过时间戳同步的问题。无人机和地面站的时间差了 2 秒,结果所有签名消息都被丢弃了。排查了半天才发现是 NTP 服务没配置好。所以我的建议是:在部署前,一定要用 mavlink_sniffer 或类似工具先验证一下时间同步是否正常。

避坑指南:时间戳窗口不要设得太宽。我见过有人为了省事把窗口设成 10 秒,结果攻击者完全可以在 10 秒内完成重放攻击。1 秒是合理的默认值,如果你的网络延迟特别大,最多放宽到 2 秒。

3.3 密钥管理:安全的核心

签名算法再强,密钥泄露了也白搭。MAVLink 的密钥管理有几个关键点:

概念 说明 我的建议
密钥 ID 1 字节,标识使用哪一组密钥 至少准备 2-3 组密钥,用于轮换
密钥长度 32 字节(256 位) 不要用更短的密钥,HMAC-SHA256 需要 32 字节输入
密钥存储 在飞控和地面站中硬编码或通过安全通道分发 绝对不要明文写在配置文件里,至少做一次 XOR 混淆
密钥轮换 定期更换密钥,旧密钥保留一段时间的接收能力 我习惯每 30 天轮换一次,保留 7 天的旧密钥接收窗口

密钥分发的挑战在于:你需要在安全的环境下把密钥写入设备。对于量产无人机,我建议在产线烧录阶段就把密钥写入飞控的加密存储区。对于地面站,可以通过加密的 USB 密钥或二维码扫描的方式导入。

小技巧:如果你用的是 PX4 或 ArduPilot,它们都内置了签名支持。你只需要在参数中设置 MAV_SIGNING_KEY 和 MAV_SIGNING_MODE 即可。但注意,这些参数在固件编译后是可以通过地面站读取的——所以生产环境中一定要禁用参数读取功能。

3.4 防止重放攻击:不止是时间戳

时间戳能防大部分重放攻击,但有一个漏洞:如果攻击者在时间窗口内截获并立即重放,时间戳检查可能通过。所以 MAVLink 还引入了另一个机制——序列号。

每个发送端维护一个 16 位的序列号,随每条签名消息递增。接收端会记录每个发送端的最新序列号,只接受序列号更大的消息。这样即使攻击者在 1 秒内重放,序列号也会暴露它。

序列号和时间戳是配合使用的:

  • 时间戳保证消息不会在长时间后被重放
  • 序列号保证消息不会被立即重放

我记得有一次调试时发现,某个设备重启后序列号重置为 0,导致地面站拒绝接收它的消息。解决方案是在设备启动时,从非易失存储中读取上次的序列号,或者发送一条特殊的「重置序列号」消息。

3.5 签名机制的完整流程

说了这么多,我们来看一张完整的签名消息处理流程图:

MAVLink 签名消息处理流程 MAVLink 签名消息处理流程 发送端 组装消息头 + 载荷 获取当前时间戳 HMAC-SHA256 计算签名 密钥 (32字节) 附加 6 字节签名到消息尾 发送签名消息 接收端 接收消息 + 签名 检查时间戳是否在窗口内 重新计算签名并比对 密钥 (32字节) 检查序列号是否递增 处理消息 任何检查失败 → 丢弃消息

从这张图可以看得很清楚:发送端和接收端的操作是对称的。接收端会用同样的密钥和同样的算法重新计算签名,然后比对。任何一个环节不匹配,消息就会被丢弃。

3.6 实际部署建议

最后,我总结几条在实际项目中验证过的经验:

  • 先测试再上线:在开发环境中先用 MAV_SIGNING_MODE=1(仅接收签名消息)测试,确认所有设备都能正常通信后再切换到生产模式。
  • 保留一个无签名通道:我习惯在调试端口上保留无签名模式,方便紧急情况下的诊断。但生产环境必须关闭。
  • 密钥泄露怎么办:准备一个应急方案。比如通过遥控器的特定通道触发密钥更新,或者通过安全链路下发新密钥。
  • 日志中不要记录密钥:这个错误我犯过。调试时把密钥打印到日志里,结果日志文件被上传到了公开的 bug 追踪系统……嗯,从那以后我写了个脚本自动扫描日志中的密钥模式。

一句话总结:MAVLink 签名机制的核心是「时间戳 + 序列号 + HMAC-SHA256」。时间戳防长时间重放,序列号防短时间重放,HMAC 保证消息完整性和来源可信。三者缺一不可。