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的流程,说白了就是设备跟服务器互相确认身份的过程。我习惯把它分成三步:
- 设备发起请求:发送Join-Request消息
- 服务器验证并响应:回复Join-Accept消息
- 密钥派生:双方各自计算出会话密钥
咱们先看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) |
+----------------+-----------------------------------+----------------+
加密过程是这样的:
- 服务器用AppKey对Join-Accept负载进行AES-128加密
- 加密后的数据加上MIC一起发送
- 设备收到后用同样的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 安全机制的核心逻辑
为了让你更直观地理解整个流程,我画了一张图:
从这张图你能看到,整个流程的核心就是:设备用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的加密机制,咱们就聊到这儿。记住一句话:入网安全是整个通信安全的基础。这个环节没做好,后面所有的加密都是白搭。