4、Join过程安全机制:OTAA与ABP安全对比、Join-Request/Join-Accept消息结构、MIC计算与验证、Join-Nonce与DevNonce防重放机制

聊到LoRaWAN的入网,很多新手第一反应就是“发个请求,等个回复”。嗯,表面看确实是这样。但如果你真在项目里这么干,大概率会被重放攻击教做人。

我最早接触LoRaWAN时,就踩过这个坑。当时为了赶进度,直接用了ABP模式,结果设备上线后老是收到乱数据。后来一查,原来是有人抓包重放了旧帧。从那以后,我对Join过程的安全机制再也不敢马虎了。

这一章,咱们就把OTAA和ABP的底裤扒干净。看看Join-Request和Join-Accept到底长什么样,MIC是怎么算的,Nonce又是怎么防重放的。

4.1 OTAA vs ABP:安全性的分水岭

先说结论:能用OTAA就别用ABP。这不是我个人的偏执,而是协议设计上的根本差异。

对比项 OTAA(空中激活) ABP(个人化激活)
密钥生成方式 动态派生,每次入网不同 预置在设备中,固定不变
会话密钥 每次Join重新生成 出厂即固定,永不更新
防重放能力 强(DevNonce + JoinNonce双重校验) 弱(仅靠FCnt,容易被绕过)
设备更换网络 支持(重新Join即可) 不支持(需重新烧录密钥)
适用场景 绝大多数生产环境 实验室测试、封闭内网
⚠️ 避坑指南:我曾经在一个智慧农业项目里,客户坚持用ABP,理由是“省流量”。结果设备部署后第三天,有人用抓包工具重放了旧数据包,导致灌溉系统误动作。最后全部返工改成OTAA,多花了三周时间。所以,ABP只适合你完全控制物理链路的场景,比如实验室内部测试。

4.2 Join-Request消息结构:设备的第一声招呼

设备要入网,第一步就是发Join-Request。这个包不长,但每个字段都有讲究。

Join-Request 消息格式(MAC层,不含PHY头):
+----------------+----------------+----------------+----------------+
|   AppEUI (8B)  |   DevEUI (8B)  | DevNonce (2B)  |    MIC (4B)    |
+----------------+----------------+----------------+----------------+
总长度:22字节
  • AppEUI:应用标识,告诉网络服务器“我想加入哪个应用”。
  • DevEUI:设备唯一标识,全球唯一,类似设备的身份证。
  • DevNonce:设备生成的随机数,2字节,用于防重放。
  • MIC:消息完整性校验码,4字节,用AppKey计算。

这里有个细节:Join-Request是明文传输的。你可能会问:“明文?那安全吗?” 别急,MIC就是干这个的。虽然内容是明文的,但MIC保证了消息的完整性和真实性。攻击者可以看见你的AppEUI和DevEUI,但他伪造不了MIC,因为他没有AppKey。

4.3 Join-Accept消息结构:服务器的回应

服务器收到Join-Request后,如果验证通过,就回复Join-Accept。这个包是加密的,用AppKey加密。

Join-Accept 消息格式(加密前):
+----------------+----------------+----------------+----------------+----------------+
| JoinNonce (3B) |  NetID (3B)    | DevAddr (4B)  | DLSettings (1B)| RxDelay (1B)  |
+----------------+----------------+----------------+----------------+----------------+
|   CFList (0或16B)  |   MIC (4B)    |
+---------------------+----------------+
总长度:17或33字节(含MIC)
  • JoinNonce:服务器生成的随机数,3字节,用于派生会话密钥。
  • NetID:网络标识,3字节。
  • DevAddr:设备在网络中的短地址,后续通信就用这个。
  • DLSettings:下行链路参数,比如数据速率、RX窗口配置。
  • RxDelay:接收窗口延迟。
  • CFList:可选,信道频率列表,用于配置设备的工作频点。
🔑 关键点:Join-Accept的加密密钥是AppKey,而不是会话密钥。会话密钥(NwkSKey、AppSKey)是在设备收到Join-Accept后,用AppKey、JoinNonce、DevNonce等参数派生出来的。这就是OTAA“动态密钥”的核心。

4.4 MIC计算与验证:谁在说谎?

MIC(Message Integrity Code)是LoRaWAN安全的第一道防线。它的计算方式不复杂,但容易出错。

对于Join-Request,MIC的计算公式如下:

MIC = aes128_cmac(AppKey, msg | 0x00)[0..3]

其中:
- aes128_cmac:基于AES-128的CMAC算法
- AppKey:应用密钥,16字节
- msg:Join-Request消息体(AppEUI + DevEUI + DevNonce)
- 0x00:一个字节的填充,表示这是Join-Request
- [0..3]:取CMAC输出的前4个字节

对于Join-Accept,MIC的计算稍有不同:

MIC = aes128_cmac(AppKey, msg | 0x01)[0..3]

注意:填充字节变成了0x01,表示这是Join-Accept

我在项目中遇到过一个问题:设备发Join-Request后,服务器一直不回复。抓包一看,MIC算错了。原因是设备端的AppKey和服务器端的不一致。这种问题最坑,因为两边都觉得自己是对的,但就是握不上手。

💡 调试技巧:如果你遇到Join失败,先检查MIC。用Wireshark抓包,看Join-Request的MIC字段。然后自己在本地用同样的AppKey和消息体算一遍。如果对不上,99%是AppKey配置错了。

4.5 Join-Nonce与DevNonce:防重放的黄金搭档

重放攻击是IoT安全的老大难问题。攻击者不需要破解你的密钥,只需要把之前抓到的合法包再发一遍,就能让设备误以为自己是合法入网。

LoRaWAN用两个Nonce来防重放:

  • DevNonce:设备生成的随机数,2字节,每次Join-Request必须不同。
  • JoinNonce:服务器生成的随机数,3字节,每次Join-Accept必须不同。

服务器会维护一个DevNonce列表,记录每个设备最近使用过的DevNonce。如果收到重复的DevNonce,直接丢弃。同样,设备也会记录JoinNonce,防止服务器被伪造。

你想想看,如果只有DevNonce,攻击者可以伪造Join-Accept。如果只有JoinNonce,攻击者可以重放Join-Request。两个一起用,才能做到双向防重放。

⚠️ 注意:DevNonce只有2字节,理论上最多65536个不重复值。如果设备频繁入网,DevNonce可能会耗尽。LoRaWAN规范建议,设备在DevNonce耗尽后,应该重新生成AppKey或者更换设备。我在一个物流追踪项目里就遇到过这个问题,设备每天入网好几次,半年后DevNonce就重复了。解决方案是优化入网逻辑,减少不必要的Join次数。

4.6 完整Join流程:一张图说清楚

说了这么多,咱们用一张SVG图把整个流程串起来。从设备上电到会话密钥生成,每一步都清清楚楚。

OTAA Join流程与安全机制 OTAA Join流程与安全机制 设备 (End Device) 网络服务器 (NS) ① 生成DevNonce Join-Request (AppEUI + DevEUI + DevNonce + MIC) ② 验证MIC ③ 检查DevNonce ④ 生成JoinNonce Join-Accept (加密: JoinNonce + NetID + DevAddr + ...) ⑤ 解密并派生密钥 ⑥ 验证Join-Accept MIC ⑦ 检查JoinNonce ⑧ 入网成功,开始通信 密钥派生公式 NwkSKey = aes128_encrypt(AppKey, 0x01 | JoinNonce | DevNonce | pad16) AppSKey = aes128_encrypt(AppKey, 0x02 | JoinNonce | DevNonce | pad16)

这张图把整个流程拆成了8步。你注意看,从设备生成DevNonce到服务器验证MIC,再到服务器生成JoinNonce并加密回复,最后设备解密并验证——每一步都在做安全校验。少了任何一步,整个系统就不安全了。

4.7 实战建议:如何选择Join模式?

说了这么多理论,最后给点实在的建议:

  1. 生产环境必须用OTAA。别为了省那几次空中的交互流量,把整个系统的安全搭进去。
  2. DevNonce的随机性很重要。有些低成本的MCU,随机数生成器质量很差,生成的DevNonce容易重复。我建议用硬件随机数发生器,或者至少混入ADC噪声。
  3. Join-Accept的超时处理。设备发完Join-Request后,如果没收到回复,应该重试。但重试间隔要随机化,避免所有设备同时重试造成网络拥塞。
  4. 记录Join历史。服务器端要记录每个设备的Join时间、DevNonce、JoinNonce。这不仅是安全需要,也是后期排查问题的依据。
📌 总结:OTAA的Join过程,本质上是设备与服务器之间的一次安全握手。通过DevNonce和JoinNonce的双向随机数校验,加上基于AppKey的MIC保护,确保了入网过程的不可伪造和不可重放。这是LoRaWAN安全架构的基石,也是你在实际项目中必须吃透的部分。