在 BLE(Bluetooth Low Energy)连接建立后,为了保障数据传输的机密性与完整性,链路层(Link Layer)可根据上层主机(Host)的请求启动加密流程。
本文将详细解析 BLE 控制器的加密握手流程、异常处理机制、AES-CCM 底层原理,并结合 Cordio 协议栈源码进行分析。
加密流程 (Encryption Procedure)
BLE 的加密状态管理主要分为两种场景:
- 对于未加密的连接:链路层将直接启动"加密开始"流程。
- 对于已加密的连接:链路层需要先执行"加密暂停"流程,然后再启动"加密开始"流程(通常是为了更换密钥)。
加密开始流程 (Encryption Start Procedure)
加密的核心在于主从设备双方协商出一致的会话密钥 (Session Key) 和 初始化向量 (IV)。这两个参数由双方各自生成的片段组合而成:
- IV (Initialization Vector) =
IVm(主) +IVs(从) - SKD (Session Key Diversifier) =
SKDm(主) +SKDs(从)
它们通过LL_ENC_REQ(加密请求)和LL_ENC_RSP(加密响应)这两个 PDU(协议数据单元)进行交换。
阶段一:主设备发送加密请求
- 主设备主机发送请求 :主设备主机(Master Host)通过 HCI 接口向链路层发送
HCI_LE_Start_Encryption命令,携带Connection_Handle、Random_Number(Rand)、Encrypted_Diversifier(EDIV) 和Long_Term_Key(LTK)。 - 主设备暂停发送数据 PDU :主设备链路层在收到指令后,会先发送完当前队列中的数据 PDU,随后暂停发送所有数据 PDU ,仅允许发送控制 PDU(如
LL_ENC_REQ)或空包。 - 主设备链路层发送请求 : 发送
LL_ENC_REQ。其中Rand,EDIV(来自 Host),SKDm,IVm(控制器内部生成的随机数)。
第二步:从设备接收加密请求
- 暂停接收数据 PDU :从设备接收到主设备的
LL_ENC_REQ之后,会暂停 TX 和 RX 数据流。从设备的链路层应完成当前数据通道 PDU 的发送,并可以完成控制器中排队的其他数据通道 PDU 的发送。在这些数据通道 PDU 被确认后,从设备的链路层只允许发送空 PDU 或LL_ENC_RSP、LL_START_ENC_REQ、LL_START_ENC_RSP、LL_TERMINATE_IND、LL_REJECT_IND或LL_REJECT_IND_EXTPDU。 - 从设备发送响应 : 发送
LL_ENC_RSP(携带SKDs和IVs)。主设备收到后也将暂停数据 PDU接收。 - 获取 LTK : 向主机发送
LL_LTK_REQ_IND,包含Master发来的Rand和EDIV。等待从设备主机回复HCI_LE_LTK_Request_Reply,提供对应的Long_Term_Key(LTK)。
第三步:密钥的组合与生成
此时,主从双方都拥有了对方的 IV 和 SKD 片段。它们将按照以下规则将各部分拼接成完整的 SKD 和 IV:
SKD = SKDm || SKDs(SKDm 的低位字节在前,SKDs 的高位字节在后)IV = IVm || IVs(IVm 的低位字节在前,IVs 的高位字节在后)
从设备的链路层会使用加密引擎,以 LTK 作为加密密钥,以拼接好的 SKD 作为输入明文,计算出本次通信的会话密钥(sessionKey) 。这个sessionKey将用于后续所有数据包的加解密。
第四步:完成加密握手
在成功生成 sessionKey 后:
- 从设备发送
LL_START_ENC_REQPDU(此包未加密),通知 Master 已准备好接收一个加密的响应。 - 主设备收到后,发送
LL_START_ENC_RSPPDU(此包已加密),并准备好收发加密数据,此时主设备进入加密状态。 - 从设备收到加密的
LL_START_ENC_RSP后,也回复一个已加密的LL_START_ENC_RSPPDU,此时从设备进入加密状态。
第五步:通知主机 :
双方链路层都会通知各自的上层主机,发送 LL_START_ENC_RSP 事件,表示连接已成功加密。
异常处理:在加密开始流程的任何阶段,如果一方收到了非预期的 PDU(例如,在协商加密时突然收到一个普通数据包),应立即断开连接,并向主机报告错误,错误码为 "Connection Terminated Due to MIC Failure" (0 x 3 D)。
成功加密时序图
sequenceDiagram participant Master_Host as 主设备主机 participant Master_Link_Layer as 主设备链路层 participant Slave_Link_Layer as 从设备链路层 participant Slave_Host as 从设备主机 Master_Host->>Master_Link_Layer: 1. HCI_LE_Start_Encryption 开始加密 Master_Link_Layer->>Master_Link_Layer: 2. 暂停数据发送,等待发送队列清空 Master_Link_Layer->>Slave_Link_Layer: 3. 发送 LL_ENC_REQ (携带 IVm, SKDm, Rand, EDIV) Slave_Link_Layer->>Slave_Link_Layer: 4. 暂停数据收发,生成 IVs, SKDs Slave_Link_Layer->>Master_Link_Layer: 5. 回复 LL_ENC_RSP (携带 IVs, SKDs) Slave_Link_Layer->>Slave_Host: 6. 发送LL_LTK_REQ_IND 事件 Slave_Host->>Slave_Link_Layer: 发送 HCI_LE_LTK_Request_Reply Master_Link_Layer->>Master_Link_Layer: 通过 LTK 和 SKD 计算会话密钥 Session Key Slave_Link_Layer->>Slave_Link_Layer: 通过 LTK 和 SKD 计算会话密钥 Session Key Slave_Link_Layer->>Master_Link_Layer: 11. 发送 LL_START_ENC_REQ (此包未加密) Master_Link_Layer->>Master_Link_Layer: 使能发送和接收加密 Master_Link_Layer->>Slave_Link_Layer: 12. 回复 LL_START_ENC_RSP (此包已加密) Master_Link_Layer->>Master_Link_Layer: 恢复数据收发 Slave_Link_Layer->>Slave_Link_Layer: 使能发送加密 Master_Link_Layer->>Master_Host: 发送LL_ENC_CHANGE_IND 事件,通知Host已加密 Slave_Link_Layer->>Master_Link_Layer: 13. 发送 LL_START_ENC_RSP (此包已加密) Slave_Link_Layer->>Slave_Link_Layer: 恢复数据收发 Slave_Link_Layer->>Slave_Host: 发送LL_ENC_CHANGE_IND 事件,通知Host已加密
异常情况
在加密流程中,可能会遇到各种异常,链路层需根据情况进行处理。
1. 从设备不支持加密
若从设备不支持加密,会直接拒绝 LL_ENC_REQ。发送 LL_REJECT_IND 或 LL_REJECT_IND_EXT。
- 错误码 :
0x1A(Unsupported Remote Feature)。 - 结果 :Master 通知 Host 加密失败,连接保持未加密状态继续通信。
2. 密钥丢失 (LTK Missing)
- 场景 A:初始加密时缺失
若 Slave Host 无法提供 LTK(例如未配对或密钥丢失),应拒绝加密。- 错误码 :
0x06(PIN or Key Missing)。 - 结果 :连接保持未加密状态。
- 错误码 :
- 场景 B:加密暂停后缺失
若在"加密暂停"流程(即更换密钥)后无法提供 LTK,属于严重安全错误。- 结果 :必须断开连接。
3. MIC 校验失败 (MIC Failure)
在加密开启后的任何阶段,如果收到解密校验失败的数据包(MIC 不匹配),或者在握手阶段收到非预期的明文/密文。
- 动作:立即断开连接。
- 错误码 :
0x3D(Connection Terminated Due to MIC Failure)。
从设备不支持加密时序图
sequenceDiagram participant Master_Host as 主设备主机 participant Master_Link_Layer as 主设备链路层 participant Slave_Link_Layer as 从设备链路层 Master_Host->>Master_Link_Layer: LE Start Encryption 请求启用加密 Master_Link_Layer->>Slave_Link_Layer: LL_ENC_REQ Slave_Link_Layer->>Master_Link_Layer: LL_REJECT_IND / LL_REJECT_IND_EXT (Unsupported Remote feature 错误码: 0x1A) Master_Link_Layer->>Master_Host: 通知:加密失败 (对方不支持)
主机未提供 LTK 时序图
sequenceDiagram participant Master_Host as 主设备主机 participant Master_Link_Layer as 主设备链路层 participant Slave_Link_Layer as 从设备链路层 participant Slave_Host as 从设备主机 Master_Link_Layer->>Slave_Link_Layer: LL_ENC_REQ Slave_Link_Layer->>Master_Link_Layer: LL_ENC_RSP Master_Link_Layer->>Master_Host: 请求 LTK Master_Host-->>Master_Link_Layer: 未提供 LTK Slave_Link_Layer->>Slave_Host: 请求 LTK Slave_Host-->>Slave_Link_Layer: 未提供 LTK Slave_Link_Layer->>Master_Link_Layer: LL_REJECT_IND / LL_REJECT_IND_EXT (错误码: PIN or key Missing) Master_Link_Layer->>Master_Host: 通知:加密失败 (密钥丢失)
加密暂停流程
如果需要在不中断连接的情况下更换加密密钥,就需要先暂停当前的加密,然后再重新开始加密流程。在暂停期间,为保护数据安全,不允许发送未加密的数据 PDU。
流程如下:
- 主设备发送
LL_PAUSE_ENC_REQPDU(加密的)来发起暂停请求。在发送前,它会确保当前的数据包已发送完毕,并暂停发送新的数据包。 - 从设备收到请求后,同样完成当前数据包的发送,然后回复一个
LL_PAUSE_ENC_RSPPDU(加密的)。同时,从设备将自己设置为准备接收未加密的数据。 - 主设备收到从设备的响应后,也将自己设置为收发都不加密 的状态,并向从设备再发送一个
LL_PAUSE_ENC_RSPPDU(这次是未加密的)。 - 从设备收到这个未加密的响应后,也切换到发送不加密的状态。
至此,双方都暂停了加密。接下来,系统将自动启动前述的"加密开始流程",以协商并启用新的会话密钥。
加密暂停时序图
sequenceDiagram participant Master_Link_Layer as 主设备链路层 participant Slave_Link_Layer as 从设备链路层 Master_Link_Layer->>Slave_Link_Layer: 1. LL_PAUSE_ENC_REQ (已加密) Slave_Link_Layer->>Master_Link_Layer: 2. LL_PAUSE_ENC_RSP (已加密) Master_Link_Layer->>Master_Link_Layer: 3. 切换到未加密模式 Master_Link_Layer->>Slave_Link_Layer: 4. LL_PAUSE_ENC_RSP (未加密) Slave_Link_Layer->>Slave_Link_Layer: 5. 切换到未加密模式 Note over Master_Link_Layer, Slave_Link_Layer: 双方现在都处于未加密状态,暂停完成。 Note over Master_Link_Layer, Slave_Link_Layer: 接下来将自动执行"加密开始流程"以启用新密钥。
异常处理 :与加密开始流程类似,如果在暂停过程中收到非预期的 PDU,也应立即断开连接,并报告 "Connection Terminated Due to MIC Failure" (0 x 3 D) 错误。
加密重新启动 (Encryption Restart) 在需要动态增加或减少安全级别时非常有用。例如,初始建立连接时安全级别较低,但在传输敏感数据时,可以使用此过程更换链路密钥并提升安全级别。
数据加密和解密
BLE 使用 AES-128 作为基础加密引擎,结合 CCM (Counter with CBC-MAC) 模式。CCM 模式同时提供加密(机密性) 和认证(完整性)。
数据加密流程
数据加密流程分为以下几个部分 :
- 准备Nonce, AAD, Session Key
- Nonce(Number Used Once) 总长度 13 字节,由以下各部分拼接:PacketCounter,39 bits,每个连接独立的计数器。每发送一个新包就 +1,重传时不增加;DirectionBit,1 bit,主设备 (Master) 发送时为 1,从设备 (Slave) 为 0;IV (初始化向量) ,8 octets,连接开始时生成的随机数,双方共享。
- AAD (Additional Authenticated Data) 。提取 Data PDU 的首字节。将其中的
NESN、SN、MD比特位强制置为 0。这是为了确保重传(导致这些位变化)不会破坏解密校验。 - Session Key: 用 LTK 作为密钥对 SKD进行 AES-128 运算。这在上文中开始加密的阶段已经准备好了。
- 生成 MIC
将 AAD 、Nonce 和 明文 Payload 填入 CCM 规范定义的 \(B_0, B_1...\) 数据块中。使用 Session Key 对这些块进行 AES-CBC-MAC 运算,生成 MIC。 - 加密 Payload 和 MIC
使用 Session Key 和 Nonce 生成密钥流 (Keystream) 。将明文 Payload 与密钥流进行 XOR(异或) 运算,得到加密后的 Payload。将步骤 2 算出的 MIC 也与密钥流的第一段进行 XOR 运算,得到加密后的 MIC。
加密流程图
graph TD subgraph "输入准备" A[原始 Payload] --> B{长度 > 0?} B -- Yes --> C[准备 Header Octet 0] C --> D[掩码处理: NESN/SN/MD 设为 0] E[Packet Counter + Direction + IV] --> F[构造 13-byte Nonce] end subgraph "生成MIC" D --> G[构造 B0, B1 块] A --> G F --> G G --> H[AES-CBC-MAC 计算] SK[Session Key] --> H H --> I[生成 4-byte MIC] end subgraph "生成密钥" F --> J[构造 A0, A1... 块] SK --> K[AES 引擎] J --> K K --> L[生成 Keystream 密钥流] end subgraph "最终组包 (Final Assembly)" A --> M[XOR 异或运算] I --> M L --> M M --> N[加密后的 Payload + 加密后的 MIC] N --> O[添加原始 Header 发送] end
Cordio 代码分析
PalCryptoAesCcmEncrypt 函数用于对发送的数据包进行软件 AES-CCM 加密。
c
/*
* 位于: platform/targets/nordic/sources/pal_crypto.c
* 功能: 执行 AES-CCM 加密
*/
bool_t PalCryptoAesCcmEncrypt(PalCryptoEnc_t *pEnc, uint8_t *pHdr, uint8_t *pBuf, uint8_t *pMic)
{
// 1. 基础检查
// 如果未启用加密,直接返回
if (!pEnc->enaEncrypt) { return FALSE; }
// 2. 准备 Nonce
// 将当前的 Packet Counter 填入 Nonce 结构中
// PAL_BB_NONCE_MODE_PKT_CNTR 模式下,计数器由软件维护
if (pEnc->nonceMode == PAL_BB_NONCE_MODE_PKT_CNTR) {
palCryptoIncPktCnt(pCb);
}
// 3. 加载密钥
// 将 Session Key 加载到硬件 ECB 引擎
palCryptoLoadEcbData(pEnc);
// 4. 计算 MIC (认证 - Authentication)
// 使用 AES-CBC-MAC 算法计算 MIC
if (pEnc->enaAuth) {
palCryptoAuthPdu(pEnc->type, pCb, pMic, pHdr, pBuf, pldLen);
}
// 5. 加密 Payload (加密 - Encryption)
// 使用 AES-CTR 模式加密数据 (异或密钥流)
palCryptoPdu(pCb, pMic, pBuf, pldLen);
// 6. 更新计数器
// 为下一个包准备 Packet Counter
if (pEnc->nonceMode == PAL_BB_NONCE_MODE_PKT_CNTR) {
palCryptoIncPktCnt(pCb);
}
return pEnc->enaAuth;
}
PalCryptoAesCcmDecrypt 函数用于对接收的数据包进行软件 AES-CCM 解密和校验。
c
/*
* 位于: platform/targets/nordic/sources/pal_crypto.c
* 功能: 执行 AES-CCM 解密与校验
*/
bool_t PalCryptoAesCcmDecrypt(PalCryptoEnc_t *pEnc, uint8_t *pBuf)
{
// 1. 基础检查
if (!pEnc->enaDecrypt) { return TRUE; }
// 2. 解密 Payload
// CTR 模式下,解密操作与加密操作数学上是对称的 (都是 XOR 密钥流)
// 这里会还原出明文 Payload 和明文 MIC
palCryptoPdu(pCb, pMic, pBuf, pldLen);
// 3. 计算预期 MIC
// 基于解密后的明文,重新计算一遍 MIC
if (pEnc->enaAuth) {
palCryptoAuthPdu(pEnc->type, pCb, actMic, pHdr, pBuf, pldLen);
}
// 4. 验证 MIC
// 将计算得到的 actMic 与包中携带的 pMic 进行比较
if (memcmp(actMic, pMic, 4) != 0) {
return FALSE; // MIC 校验失败,Controller 将断开连接
}
return TRUE;
}
参考文献
- https://blog.csdn.net/weixin_43946212/article/details/108116251
- Bluetooth 4.2 Spec:
- Vol 6, PartB, 5.1.3 Encryption procedure
- Vol 6, PartD, 5 Initiating State
- Vol 6, Part E Low Energy Link Layer Security
- https://github.com/apache/mynewt-nimble/tree/Master
- https://github.com/packetcraft-inc/stacks
- https://www.scribd.com/document/582984411/BLE4-0低功耗蓝牙协议完全解析#sidebar