【蓝牙精品系列文档】【03_blemesh架构】07_网络层_2

网络层关键字段详解(IVI / NID / SEQ / IV Index)

本文档对 Mesh Network PDU 头部的关键字段做深入解析,包括空口识别、包交互、用途,以及全网更新流程(IV Update / Key Refresh)。


一、Network PDU 头部回顾

┌──────────┬──────────┬────────┬─────────┬─────────┬───────────┬──────┐ │ Byte 0 │ Byte 1 │ Byte2~4│ Byte5~6 │ Byte7~8 │ Byte9+ │ 尾部 │ │ IVI+NID │ CTL+TTL │ SEQ │ SRC │ DST │ Transport │NetMIC│ │ 1 B │ 1 B │ 3 B │ 2 B │ 2 B │ PDU │4/8B │ └──────────┴──────────┴────────┴─────────┴─────────┴───────────┴──────┘

字段宏定义(源码net.c):

#defineIVI(pdu)((pdu)[0]>>7)/* Byte 0 bit7 */#defineNID(pdu)((pdu)[0]&0x7f)/* Byte 0 bit0~6 */#defineCTL(pdu)((pdu)[1]>>7)/* Byte 1 bit7 */#defineTTL(pdu)((pdu)[1]&0x7f)/* Byte 1 bit0~6 */#defineSEQ(pdu)(sys_get_be24(&pdu[2]))/* Byte 2~4 大端 */#defineSRC(pdu)(sys_get_be16(&(pdu)[5]))#defineDST(pdu)(sys_get_be16(&(pdu)[7]))
字段长度是否混淆是否加密空口可见
IVI1 bit明文明文✅ 直接看
NID7 bit明文明文✅ 直接看
CTL1 bit混淆-❌ 需 Privacy Key
TTL7 bit混淆-❌ 需 Privacy Key
SEQ24 bit混淆-❌ 需 Privacy Key
SRC16 bit混淆-❌ 需 Privacy Key
DST16 bit明文加密部分可见(加密后)
Transport PDU变长-AES-CCM❌ 需 EncKey
NetMIC4/8B-加密生成❌ 需 EncKey

重点:IVI 和 NID 是空口上唯一能直接看到的头部字段,这是为了让接收方快速选 NetKey 而设计的。


二、IVI(IV Index 最低位)

字段定义

长度1 bit
位置Byte 0 bit7
是否混淆明文
取值0 或 1,即 IV Index 的最低位

空口识别

Byte 0: ┌───┬─────────────────────────────────────────┐ │IVI│ NID (7 bit) │ │1b │ │ └───┴─────────────────────────────────────────┘ ↑ IV Index 最低位 例:IV Index = 0x00000005 → IVI = 1 IV Index = 0x00000006 → IVI = 0

用途:快速选 IV Index 版本

问题背景:IV Update 期间,节点同时持有两个 IV Index(旧的和新的),需要判断收到的包用哪个 IV Index 解密。

IVI 的作用:接收方先看 IVI,快速判断是「新 IV」还是「旧 IV」:

/* net.c - net_decrypt() */rx->old_iv=(IVI(in->om_data)!=(bt_mesh.iv_index&0x01));
  • IVI == 本地 iv_index 最低位新 IVold_iv = false
  • IVI != 本地 iv_index 最低位旧 IVold_iv = true

然后选用对应的 IV Index 构造 Nonce 解密:

/* net.c */#defineBT_MESH_NET_IVI_RX(rx)((rx)->old_iv?\(bt_mesh.iv_index-1):bt_mesh.iv_index)

包交互示例

关键前提:节点进入 IV Update in Progress 后,bt_mesh.iv_index已经是 +1 后的新值(源码bt_mesh.iv_index = iv_index)。所以「持有 5 和 6」的节点 B,其bt_mesh.iv_index = 6,不是 5。

节点 A (Normal, iv_index=5) 节点 B (IV Update, iv_index=6, 持有 5 和 6) │ │ │ Network PDU (A 用 IV=5 发) │ │ [IVI=1][NID=...] ← 5 & 1 = 1 │ ───────────────────────────> │ B: iv_index=6, 6 & 1 = 0 │ │ old_iv = (IVI=1 != 0) = true │ │ → 用 IV=6-1=5 解密 ✅(B 持有旧 IV=5) │ │ │ (B 用 IV=6 发包) │ │ [IVI=0][NID=...] ← 6 & 1 = 0 │ <─────────────────────────── │ A 视角:iv_index=5, 5 & 1 = 1 │ │ old_iv = (IVI=0 != 1) = true │ │ → 按「旧 IV」规则用 IV=5-1=4 试解 ❌ │ │ → A 只持有 IV=5,没有 4,解密失败,丢弃 │ │ │ │ 实际情况:这个包是 B 用新 IV=6 发的 │ │ A 的 iv_index 还停在 5,不认识 IV=6 │ │ A 的 old_iv 逻辑只能处理「比本地旧 1」的包 │ │ 无法处理「比本地新 1」的包 → 当成旧 IV=4 试解 │ │ │ │ 解决:A 必须先收到 Secure Network Beacon │ │ 同步到 iv_index=6 后,才能解 B 的包: │ │ 同步后 A: iv_index=6, 6&1=0 │ │ → IVI=0 匹配 → old_iv=false → 用 IV=6 解 ✅

关键规则:

  • old_iv = true表示「这是旧 IV 的包」,接收方用iv_index - 1解密
  • old_iv = false表示「这是新 IV 的包」,接收方用iv_index解密
  • IV Update 期间节点能解新旧两个 IV(持有iv_indexiv_index-1
  • Normal 状态节点只能解当前 IV,收不到新 IV 的包(需通过 Beacon 同步后才能)

为什么只用 1 bit

  • IV Index 是 32 bit,但空口只暴露最低位
  • 隐私:不暴露完整 IV Index,避免攻击者追踪网络年龄
  • 效率:1 bit 足够区分「新旧 IV」两种状态
  • 完整 IV Index 通过 Secure Network Beacon 同步(带 32-bit IV Index 字段)

三、NID(Network ID)

字段定义

长度7 bit
位置Byte 0 bit0~6
是否混淆明文
取值范围0x00 ~ 0x7F(128 种)
派生k2(NetKey) 派生

派生过程

NetKey (16B) │ └── k2 派生 ──┬── NID (7 bit) ← 网络标识 ├── Encryption Key ← AES-CCM 加密 └── Privacy Key ← XOR 混淆

源码subnet.c

staticintmsg_cred_create(structbt_mesh_net_cred*cred,constuint8_t*p,size_tp_len){returnbt_mesh_k2(key,p,p_len,&cred->nid,cred->enc,cred->privacy);}

NID、EncKey、PrivacyKey 三者一起派生,绑定为同一个bt_mesh_net_cred结构体。

空口识别

Byte 0: ┌───┬─────────────────────────────────────────┐ │IVI│ NID (7 bit) │ │ │ 0x00 ~ 0x7F │ └───┴─────────────────────────────────────────┘

用途:快速选 NetKey 凭证

问题背景:一个节点可能持有多个 NetKey(多子网),收到包时要判断用哪个 NetKey 解密。如果不用 NID,得对每个 NetKey 都试一遍 AES-CCM 解密,开销巨大。

NID 的作用:粗筛——只有 NID 匹配的凭证才尝试解密:

/* net.c - net_decrypt() */if(NID(in->om_data)!=cred->nid){returnfalse;/* NID 不匹配,直接跳过,不试解密 */}

包交互示例

节点持有 3 个 NetKey: NetKey A → NID = 0x12, EncKey A, PrivKey A NetKey B → NID = 0x34, EncKey B, PrivKey B NetKey C → NID = 0x56, EncKey C, PrivKey C 收到空口包 [IVI=0][NID=0x34]... 遍历 3 个凭证: ├─ NID 0x12 != 0x34 → 跳过(不试解密 A) ├─ NID 0x34 == 0x34 → 匹配!用 EncKey B 解密 └─ NID 0x56 != 0x34 → 跳过(不试解密 C) 省了 2 次 AES-CCM 解密运算

NID 碰撞问题

NID 只有 7 bit = 128 种,两个不同 NetKey 可能派生出相同 NID(概率 1/128)。

碰撞时怎么办:

  • NID 匹配的凭证可能多个 → 都试解密
  • 真正区分靠NetMIC 校验:错误 NetKey 解密后 MIC 不对,包被丢弃
  • NID 只是粗筛,不能单靠 NID 认定「这是我的网」

Friendship NID

Friendship 建立后会派生独立的 Friendship 凭证(含独立 NID):

原 NetKey 凭证:NID = 0x12(全网泛洪用) Friendship 凭证:NID = 0x47(LPN↔Friend 私聊用)

LPN 和 Friend 通信时用 Friendship NID,其他节点看到 NID=0x47 不匹配自己的任何凭证,直接丢弃,实现私聊隔离

NID vs Network ID(重要区分)

NID 和 Network ID 是两个完全不同的东西,容易混淆。

维度NIDNetwork ID
派生算法k2k3
长度7 bit8 字节(64 bit)
出现位置Network PDU 头部 Byte 0Secure Network Beacon / Proxy 广播
用途快速识别用哪个 NetKey 凭证解密标识子网(Beacon 里)
空口可见✅ 明文(每个 0x2A 包都有)✅ 明文(在 0x2B Beacon 里)
取值空间128 种(易碰撞)2^64 种(几乎不碰撞)
源码证据

NID 来自 k2 派生(7 bit,存在bt_mesh_net_cred):

structbt_mesh_net_cred{uint8_tnid;/* NID: 7 bit */uint8_tenc[16];/* Encryption Key */uint8_tprivacy[16];/* Privacy Key */};

Network ID 来自 k3 派生(8 字节,存在 subnet keys):

/* subnet.h */structbt_mesh_subnet_keys{...uint8_tnet_id[8];/* Network ID: 8 字节 */};/* subnet.c */err=bt_mesh_k3(key,keys->net_id);/* k3 派生 Network ID */
出现在空口的不同位置
┌─────────────────────────────────────────────────────────────┐ │ 0x2A Network PDU │ │ ┌──────────┬─────┬──────┬──────┬────────┬──────┬─────┐ │ │ │ IVI+NID │ CTL │ TTL │ SEQ │ SRC │ DST │ ... │ │ │ │ 1 B │ 1B │ 7b │ 24b │ 16b │ 16b │ │ │ │ └──────────┴─────┴──────┴──────┴────────┴──────┴─────┘ │ │ ↑ │ │ NID (7 bit) ← k2 派生,每个 Network PDU 都有 │ └─────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────┐ │ 0x2B Secure Network Beacon │ │ ┌──────────┬───────┬────────────┬────────────┬──────────┐ │ │ │BeaconType│ Flags │ Network ID │ IV Index │ Auth Val │ │ │ │ 0x01 │ 1B │ 8B │ 4B │ 8B │ │ │ └──────────┴───────┴────────────┴────────────┴──────────┘ │ │ ↑ │ │ Network ID (8B) ← k3 派生,只在 Beacon 里 │ └─────────────────────────────────────────────────────────────┘
为什么需要两个不同的标识
标识解决的问题
NID(7 bit)Network PDU 每秒可能几十上百个,需要极快粗筛用哪个 NetKey 解密。7 bit 够了,碰撞靠 MIC 兜底
Network ID(8B)Beacon 频率低(10 秒一次),需要精确识别子网,8 字节几乎不碰撞,用于 Secure Network Beacon 验证 + Proxy 节点发现

设计哲学:

  • NID 是「快但不精确」——为每个 Network PDU 节省解密开销
  • Network ID 是「慢但精确」——为低频 Beacon 提供可靠子网识别
Proxy 广告也用 Network ID

源码proxy_srv.c

memcpy(proxy_svc_data+3,sub->keys[SUBNET_KEY_TX_IDX(sub)].net_id,8);

Proxy 节点广播时把 Network ID 放在 Service Data 里,手机扫描到后用 Network ID 判断「这个 Proxy 能接入我要的子网吗」。

NetKey 完整派生家族

NetKey 是网络层所有密钥的,通过 k1/k2/k3 不同算法派生出不同用途的密钥:

Network Key (16 B) │ ├── k2 派生 ──┬── NID (7 bit) ← 网络标识(Network PDU 粗筛) │ ├── Encryption Key (16B) ← Network PDU 加密(AES-CCM) │ └── Privacy Key (16B) ← Network PDU 混淆(XOR) │ ├── k3 派生 ──── Network ID (8 B) ← Secure Network Beacon 标识子网 │ ├── k1 派生 ──── Identity Key (16B) ← Node Identity 广播(Proxy 发现具体节点) │ 用途:Proxy 节点配网后短期广播,让手机发现自己 │ 算法:AES-ECB(IdentityKey, Random+Addr) → Hash │ 位置:Proxy 广播 Service Data │ 防护:只有有 NetKey 的手机能验证,知道是哪个节点 │ └── k1 派生 ──── Beacon Key (16B) ← Secure Network Beacon 认证(防伪造) 用途:计算 Beacon 的 Auth Value 算法:AES-CMAC(BeaconKey, Flags+NetID+IVIdx) → 8B 位置:0x2B Beacon 尾部 Auth Value 字段 防护:没有 Beacon Key 无法伪造合法 Beacon
派生算法输出用途出现位置
k2NID + EncKey + PrivacyKeyNetwork PDU 收发(最常用)0x2A Network PDU
k3Network ID(8B)Secure Network Beacon 标识子网0x2B Beacon
k1Identity KeyNode Identity 广播(配网后短期可发现)Proxy 广播
k1Beacon KeySecure Network Beacon 的 Auth Value 计算0x2B Beacon
Friendship 凭证也由 NetKey 派生

LPN 和 Friend 建立 Friendship 时,派生独立的 Friendship 凭证,输入还是 NetKey,但加了额外参数:

intbt_mesh_friend_cred_create(structbt_mesh_net_cred*cred,uint16_tlpn_addr,uint16_tfrnd_addr,uint16_tlpn_counter,uint16_tfrnd_counter,constuint8_tkey[16]){uint8_tp[9];p[0]=0x01;sys_put_be16(lpn_addr,p+1);sys_put_be16(frnd_addr,p+3);sys_put_be16(lpn_counter,p+5);sys_put_be16(frnd_counter,p+7);returnmsg_cred_create(cred,p,sizeof(p),key);/* key 就是 NetKey */}
Friendship Key = k2(NetKey, [0x01|LPNAddr|FriendAddr|LPNCounter|FriendCounter]) → Friendship NID + Friendship EncKey + Friendship PrivacyKey

根都是 NetKey,只是派生参数不同,产生不同的凭证组合。

Identity Key 详解(Node Identity 广播)

用途:Proxy 节点配网后短期广播,让手机发现自己。

派生(源码crypto.h
staticinlineintbt_mesh_identity_key(constuint8_tnet_key[16],uint8_tidentity_key[16]){returnbt_mesh_id128(net_k