3、密钥体系深度解析:AppKey与NwkKey的生成与分发、会话密钥的派生机制、密钥生命周期管理
聊到LoRaWAN安全,密钥体系是绕不开的核心。说白了,整个安全架构就是建立在几把钥匙之上的。我刚开始接触LoRaWAN时,也经常把AppKey和NwkSKey搞混,觉得不就是几个密钥嘛,能有多复杂?直到在一次实际部署中,因为密钥分发环节出了纰漏,导致整个网络需要重新入网……嗯,从那以后,我对密钥的敬畏心就上来了。
这一章,我们就来把这套密钥体系彻底拆开揉碎。你想想看,一把钥匙开一把锁,LoRaWAN里到底有几把锁?它们是怎么造出来的?又是怎么用的?
3.1 密钥的“根”:AppKey与NwkKey
在LoRaWAN 1.0.x版本中,只有一个根密钥叫AppKey。但从1.1版本开始,为了安全解耦,分成了两个根密钥:AppKey和NwkKey。我个人习惯把这两个密钥称为“主密钥”或“根密钥”,因为它们是一切会话安全的起点。
核心区别一句话总结:
- NwkKey:负责网络层面的安全,派生网络会话密钥(NwkSKey)。
- AppKey:负责应用层面的安全,派生应用会话密钥(AppSKey)。
为什么要分开?我遇到过这样一个场景:一个智慧农业项目,传感器数据需要加密,但网络运营商只负责数据转发,不应该看到数据内容。如果只有一个AppKey,网络侧和终端侧共享同一个根密钥,那网络侧理论上也能解密应用数据。分开后,NwkKey只用于网络层,AppKey只用于应用层,各司其职,互不干扰。
3.1.1 密钥的生成
这两个根密钥是怎么来的?不是设备自己算出来的,也不是网络服务器随机生成的。它们是在设备个性化阶段,由设备制造商或系统集成商预置进去的。
生成方式通常有两种:
- 随机生成:使用硬件安全模块(HSM)或密码学安全的伪随机数生成器(CSPRNG)生成128位随机数。这是最推荐的方式。
- 派生生成:基于设备的唯一标识(如DevEUI)和主密钥种子,通过KDF(密钥派生函数)计算得出。这种方式便于批量管理,但安全性依赖于种子的保护。
我的建议: 千万别用固定密钥或简单规律(如DevEUI + 123456)来生成根密钥。我曾经审计过一个项目,发现所有设备的AppKey都是DevEUI的SHA256前16字节……这跟没锁门没啥区别。
3.1.2 密钥的分发
分发是密钥管理中最脆弱的一环。根密钥需要同时存储在终端设备和网络服务器(或Join Server)中。分发方式主要有以下几种:
| 分发方式 | 安全性 | 适用场景 | 我的评价 |
|---|---|---|---|
| 工厂预置 | 高 | 大规模量产 | 最安全,但需要信任工厂的流程 |
| 安全通道下发 | 中高 | 小批量或原型验证 | 依赖TLS等通道的安全性 |
| 物理接触(串口/NFC) | 高 | 现场部署 | 麻烦,但可控 |
| 空中分发(OTAA) | 低 | 不推荐用于根密钥 | 根密钥本身不能通过空中分发,这是鸡生蛋的问题 |
注意: 根密钥一旦泄露,整个设备的安全体系就崩塌了。所有基于该根密钥派生的会话密钥都可以被计算出来。所以,根密钥的存储必须使用安全元件(SE)或可信执行环境(TEE)。
3.2 会话密钥的派生机制
根密钥是“长期密钥”,不能直接用于加密每一帧数据。原因很简单:如果长期密钥被用于加密,攻击者可以通过收集大量密文来尝试破解。所以,LoRaWAN设计了一套会话密钥派生机制,每次入网(Join)都会生成新的会话密钥。
这个过程发生在Join过程中。终端设备发送Join Request,Join Server验证通过后,会计算并下发Join Accept消息。而会话密钥,就是在这个阶段由终端和Join Server各自独立计算出来的。
3.2.1 派生的数学基础
LoRaWAN使用AES-CMAC作为伪随机函数(PRF)来派生密钥。具体来说,它定义了一个名为DeriveKey的函数:
// 伪代码:LoRaWAN 1.1 会话密钥派生
// 输入:根密钥(NwkKey 或 AppKey),类型标识(Type),JoinNonce,JoinEUI,DevNonce
function DeriveKey(RootKey, Type, JoinNonce, JoinEUI, DevNonce):
// 1. 构建输入字符串
input = Type || JoinNonce || JoinEUI || DevNonce || pad16
// 2. 使用AES-CMAC计算
return AES_CMAC(RootKey, input)
这里的Type是一个单字节标识符,用于区分派生的是哪个会话密钥:
0x01:用于派生 NwkSKey(网络会话密钥)0x02:用于派生 AppSKey(应用会话密钥)0x03:用于派生 FNwkSIntKey(前向网络会话完整性密钥)0x04:用于派生 SNwkSIntKey(后向网络会话完整性密钥)
关键点: 派生过程不需要网络传输密钥本身。终端和Join Server各自拥有根密钥,并共享Join过程的随机数(JoinNonce、DevNonce),因此可以独立计算出完全相同的会话密钥。这就是“密钥协商”的精髓。
3.2.2 会话密钥的用途
派生出来的会话密钥各有分工:
| 会话密钥 | 长度 | 用途 | 谁持有 |
|---|---|---|---|
| NwkSKey | 128 bit | 加密网络层负载(MAC命令) | 终端 + 网络服务器 |
| AppSKey | 128 bit | 加密应用层负载(传感器数据等) | 终端 + 应用服务器 |
| FNwkSIntKey | 128 bit | 计算上行消息的MIC(完整性校验) | 终端 + 网络服务器 |
| SNwkSIntKey | 128 bit | 计算下行消息的MIC | 终端 + 网络服务器 |
你可能会问:为什么完整性校验还要分两个密钥?这是LoRaWAN 1.1的一个改进。FNwkSIntKey用于终端发给网络服务器的消息,SNwkSIntKey用于网络服务器发给终端的消息。这样设计,可以防止某些类型的重放攻击。我在一个项目中就遇到过,因为只用一个完整性密钥,攻击者截获了下行消息并重放,导致终端执行了重复的指令。分开后,这种攻击就失效了。
3.3 密钥生命周期管理
密钥不是永久的。从生成到销毁,每个阶段都需要精心管理。我把密钥生命周期分为四个阶段:生成 → 分发 → 使用 → 销毁。
下面这张图可以帮你直观理解整个密钥体系的流转关系:
3.3.1 密钥使用中的注意事项
在实际使用中,有几个坑需要特别注意:
- 会话密钥的时效性:会话密钥只在当前会话中有效。一旦设备重新入网(发送新的Join Request),旧的会话密钥立即失效。我建议在设备固件中实现一个“强制重新入网”机制,比如每24小时或每发送1000帧数据后,主动触发一次重新入网。
- 密钥的轮换:虽然根密钥是长期有效的,但并不意味着永远不换。如果设备被物理破解,根密钥可能泄露。所以,对于高安全等级的场景,应该支持“远程密钥更新”机制。LoRaWAN的
KeyUpdate命令就是干这个的。 - 密钥的备份与恢复:网络服务器端必须安全备份所有设备的根密钥。一旦服务器硬盘损坏,所有设备都需要重新入网,这在大型部署中是灾难性的。我习惯使用HSM来管理服务器端的密钥,既保证安全,又支持备份。
我曾经踩过的坑: 在一次智能抄表项目中,我们为了方便调试,在终端固件中硬编码了AppKey,并且没有做任何保护。结果一个测试人员不小心把固件上传到了公开的代码仓库……嗯,那批设备全部需要返厂更换密钥。从那以后,我坚持所有量产设备的根密钥必须通过安全元件存储,并且固件中只保留“密钥索引”,而不是密钥本身。
3.3.2 密钥销毁
密钥销毁是生命周期中容易被忽视的一环。当设备报废、退网或密钥泄露时,必须及时销毁密钥。
- 终端侧:通过安全擦除命令,将存储根密钥和会话密钥的内存区域覆写为随机数。简单的删除文件是不够的,因为数据可能还在闪存的“残留区”。
- 服务器侧:从数据库中删除或标记为“已吊销”。同时,需要记录密钥的吊销日志,以备审计。
嗯,密钥体系这块内容确实比较抽象,但它是整个LoRaWAN安全的基石。你只要记住一个核心原则:根密钥保平安,会话密钥保通信。根密钥藏好,会话密钥勤换,你的网络就安全了一大半。