3、Join过程安全机制:OTAA与ABP入网流程对比、Join-Request/Accept消息加密

好,咱们今天聊聊LoRaWAN入网这块的安全机制。说实话,这是整个协议里最容易出问题的地方,也是我当年踩坑最多的环节。

你想想看,一个传感器装好了,上电第一件事是什么?是找网络。怎么找?怎么证明自己是合法的?怎么防止别人冒充?这些问题的答案,都藏在Join过程里。

3.1 OTAA vs ABP:两种入网方式的本质区别

LoRaWAN定义了两种入网方式:OTAA(Over-The-Air Activation)和ABP(Activation By Personalization)。我个人的习惯是,能用OTAA就别用ABP,除非你有特别硬的理由。

为什么?咱们先看个对比表:

对比项 OTAA ABP
入网流程 设备发送Join-Request,服务器回复Join-Accept 设备直接使用预置的DevAddr、NwkSKey、AppSKey
密钥生成 动态派生,每次入网不同 静态预置,永远不变
安全性 高,有双向认证 低,无认证过程
适用场景 大多数生产环境 测试、演示、或极端低功耗场景
设备重置 可重新入网,密钥刷新 密钥固定,泄露后无法更换

我在项目中遇到过一家厂商,为了省事把所有设备都配成了ABP。结果呢?有个客户把设备拆了,读出了Flash里的密钥,然后伪造了上千个假设备往网络里灌数据。嗯,那场面,真是惨不忍睹。

⚠️ 重要警告:

ABP模式下,NwkSKey和AppSKey是静态的。一旦泄露,攻击者可以伪造任何数据包。而且你没法远程更新这些密钥——除非你OTA升级固件,但OTA本身又依赖这些密钥。这就成了死循环。

3.2 OTAA入网流程详解

OTAA的流程,说白了就是设备跟服务器互相确认身份的过程。我习惯把它分成三步:

  1. 设备发起请求:发送Join-Request消息
  2. 服务器验证并响应:回复Join-Accept消息
  3. 密钥派生:双方各自计算出会话密钥

咱们先看Join-Request消息的结构:

Join-Request 消息格式:
+----------------+----------------+----------------+----------------+
|   AppEUI (8B)  |   DevEUI (8B)  | DevNonce (2B)  |    MIC (4B)    |
+----------------+----------------+----------------+----------------+

这里有个关键点:DevNonce。它是一个随机数,每次入网必须不同。服务器会记录所有用过的DevNonce,如果发现重复,直接拒绝。

为什么会这样?防止重放攻击啊!我曾经见过一个测试环境,设备每次重启都发同样的DevNonce,结果服务器以为有人在攻击,直接把设备拉黑了。折腾了半天才发现是代码里忘了更新随机数种子。

💡 个人经验:

DevNonce的随机性很重要。别用简单的递增计数器,也别用时间戳。我一般用硬件随机数发生器,实在没有的话,用ADC读取悬空引脚的噪声也行。

3.3 Join-Accept消息加密

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

Join-Accept的结构如下:

Join-Accept 消息格式(加密前):
+----------------+----------------+----------------+----------------+
|   AppNonce (3B)|   NetID (3B)   | DevAddr (4B)   | DLSettings (1B)|
+----------------+----------------+----------------+----------------+
|   RxDelay (1B) |   CFList (0/16B)                 |    MIC (4B)    |
+----------------+-----------------------------------+----------------+

加密过程是这样的:

  1. 服务器用AppKey对Join-Accept负载进行AES-128加密
  2. 加密后的数据加上MIC一起发送
  3. 设备收到后用同样的AppKey解密

这里有个细节:AppNonce。它是一个3字节的随机数,由服务器生成。设备收到后,会用AppNonce加上其他参数,通过AES算法派生出NwkSKey和AppSKey。

密钥派生公式:

NwkSKey = aes128_encrypt(AppKey, 0x01 | AppNonce | NetID | DevNonce | pad16)
AppSKey = aes128_encrypt(AppKey, 0x02 | AppNonce | NetID | DevNonce | pad16)

注意看,两个密钥的派生参数只差一个字节(0x01 vs 0x02)。这就是为什么它们不同,但又互相关联。

3.4 安全机制的核心逻辑

为了让你更直观地理解整个流程,我画了一张图:

OTAA 入网安全流程 终端设备 网络服务器 ① Join-Request AppEUI + DevEUI + DevNonce 验证DevNonce唯一性 检查AppKey是否匹配 ② Join-Accept(加密) AppNonce + NetID + DevAddr 用AppKey解密 验证MIC 派生会话密钥 NwkSKey + AppSKey 派生会话密钥 NwkSKey + AppSKey

从这张图你能看到,整个流程的核心就是:设备用AppKey证明身份,服务器用AppNonce保证新鲜度。两者缺一不可。

3.5 常见陷阱与避坑指南

做了这么多年LoRaWAN,我总结了几条OTAA的常见坑:

  • DevNonce重复:有些MCU的随机数生成器质量太差,导致DevNonce重复。服务器会拒绝,设备就永远入不了网。
  • AppKey配置错误:设备和服务器的AppKey必须一致。我曾经因为大小端问题,折腾了两天才发现密钥写反了。
  • Join-Accept超时:设备发送Join-Request后,必须在RX1或RX2窗口内收到Join-Accept。如果网络延迟大,设备会以为入网失败,反复重试。

🔧 我的调试技巧:

如果你怀疑入网有问题,先用抓包工具看空中的原始数据。检查Join-Request里的DevNonce是不是每次都不一样。再看Join-Accept的MIC是否正确。很多时候,问题就出在这些细节上。

好了,关于OTAA和ABP的对比,以及Join-Request/Accept的加密机制,咱们就聊到这儿。记住一句话:入网安全是整个通信安全的基础。这个环节没做好,后面所有的加密都是白搭。


专注资料整理