BES BLE CTKD 完整学习笔记

BLE CTKD 完整学习笔记

基于 Bluetooth Core Spec 5.4、固件代码(BES BEST1603)整理

覆盖:基础概念 / SMP 配对原理 / LTK 机制 / LL 加密过程 / CTKD 原理与流程 / iOS vs Android 差异 / iOS SMP 失败恢复 / 排错指南


一、基础概念速查

1.1 密钥体系总览

缩写 全称 作用 所属层
LTK Long Term Key BLE 重连加密的主密钥,16 字节 BLE SMP
EDIV Encrypted Diversifier LTK 的查找索引(Legacy Pairing 用),2 字节 BLE SMP
Rand Random Number 与 EDIV 配合定位 LTK,8 字节 BLE SMP
IRK Identity Resolving Key 解析对端的随机地址(RPA),16 字节 BLE SMP
CSRK Connection Signature Resolving Key 数据签名验证,16 字节 BLE SMP
Link Key BR/EDR Link Key 经典蓝牙连接加密密钥,16 字节 BR/EDR
STK Short Term Key Legacy Pairing 临时会话密钥(不存储) BLE SMP
DHKey Diffie-Hellman Key SC Pairing 中 ECDH 协商的共享密钥 BLE SMP
LK Link Key(CTKD 语境) 经 CTKD 从 BLE LTK 派生的 BR/EDR Link Key CTKD

1.2 SMP 是什么

SMP(Security Manager Protocol) 是 BLE 协议栈中负责配对和密钥分发的协议,跑在 L2CAP 固定信道(CID 0x0006)上。

SMP 的核心任务:

  1. 配对(Pairing):协商安全参数,生成会话密钥
  2. 绑定(Bonding):将长期密钥(LTK/IRK/CSRK)持久化存储,用于后续重连
  3. 加密(Encryption):用 LTK 启动链路层加密

1.3 LTK 是什么,和 SMP 的关系

复制代码
SMP 是协议(过程)
LTK 是结果(密钥)
  • SMP 配对成功后,双方各自生成并交换 LTK
  • LTK 持久化存储在 NV(非易失存储)中
  • 重连时不再走 SMP 配对,直接用存储的 LTK 启动链路层加密(LL Encryption)
  • Central(手机)用 EDIV+Rand 向 Peripheral(耳机)请求 LTK,耳机从 NV 查找并回复

关键区分

场景 是否走 SMP 是否使用 LTK
首次连接(未配对) 走 SMP Pairing 配对完成后生成并存储 LTK
已配对设备重连 不走 SMP 直接用已存的 LTK 加密
配对信息丢失一方 走 SMP 重新配对 生成新 LTK

这就是为什么安卓正常连接 log 里看不到 [SMP_REQUIRE][SMP_PAIR_CMP]------安卓已和耳机配对过,重连时直接走 LTK 加密,SMP 事件根本不发生。

1.4 LTK 的生成原理(Spec 精确定义)

LTK 的生成方式因配对模式不同而有本质区别。

SC Pairing 中的 LTK(推荐,CTKD 必须使用)

规范参考 :Core Spec 5.4 Vol 3 Part H § 2.2.8(f5 函数)

SC Pairing 用 ECDH 共享密钥 DHKey 通过密码学函数 f5 同时派生 MacKeyLTK

复制代码
f5(W, N1, N2, A1, A2) → { MacKey, LTK }

输入:
  W   = DHKey(ECDH P-256 协商的 256 位共享密钥)
  N1  = Initiator 的随机数 Na(128 位)
  N2  = Responder 的随机数 Nb(128 位)
  A1  = Initiator 蓝牙地址(含类型,7 字节)
  A2  = Responder 蓝牙地址(含类型,7 字节)

内部实现(双 AES-CMAC):
  T       = AES-CMAC_SALT("btle")   其中 SALT = 6C888391AAF5A538
  MacKey  = AES-CMAC_T( counter=0 || keyID="ltk " || Swapped(N1) || Swapped(N2) || A1 || A2 || length=256 )
  LTK     = AES-CMAC_T( counter=1 || keyID="ltk " || Swapped(N1) || Swapped(N2) || A1 || A2 || length=256 )
  • 双方独立推算 :Initiator(手机)和 Responder(耳机)各自拥有相同的 DHKey、随机数和地址,可以独立计算出完全相同的 LTK,无需传输 LTK 本体
  • MacKey 用于 DHKey Check (Phase 2 最后的 f6 认证验证,防中间人攻击)
  • SC 模式下 EDIV=0, Rand=0:LTK 不依赖 EDIV/Rand 索引,LL 加密请求时 EDIV 和 Rand 字段均填 0
Legacy Pairing 中的 LTK(已过时,不支持 CTKD)

规范参考:Core Spec 5.4 Vol 3 Part H § 2.3.5.1

Legacy Pairing 的 LTK 由 Responder(耳机)随机生成,再通过 STK 加密后分发给 Initiator:

复制代码
配对流程:
  1. 双方计算 STK(Short Term Key):
     STK = s1(TK, Srand, Mrand)
         其中 TK = 临时密钥(Just Works 下为全0,Passkey Entry 下为6位数字)

  2. 用 STK 加密 L2CAP 通道

  3. Responder 生成随机 LTK + 随机 EDIV + 随机 Rand

  4. Phase 3 密钥分发:
     Responder → Initiator:
       Encryption Information PDU  包含 LTK(用加密通道传输)
       Central Identification PDU  包含 EDIV + Rand
  • LTK 通过链路传输:明文 LTK 在加密信道内传输(STK 保护),但 STK 本身易被暴力破解
  • EDIV+Rand 是 LTK 的索引键:重连时 Central 发送 EDIV+Rand,Peripheral 从 NV 查找对应的 LTK
  • 不支持 CTKD:Legacy Pairing 缺乏 DHKey,h6/h7 派生 BR/EDR Link Key 的安全证明不成立,规范明确禁止
两种模式对比
特性 SC Pairing Legacy Pairing
规范基础 BT 4.2+,§ 2.3.5.6 BT 4.0,§ 2.3.5.1
LTK 生成方式 双方从 DHKey 独立计算(f5 Responder 随机生成,加密分发
LTK 在链路上传输? (双方各自推算) 是(STK 加密保护)
EDIV/Rand 用途 SC 下填 0,仅兼容用 实际索引,查 NV 找 LTK
是否支持 CTKD
安全强度 256-bit ECDH,抗被动窃听 STK 可被暴力破解,不推荐

二、SMP 配对详解

2.1 两种配对方式

复制代码
BLE 配对方式
├── Legacy Pairing(BT 4.0/4.1)
│   └── 生成 STK → 用 STK 加密 → 分发 LTK
└── Secure Connections (SC) Pairing(BT 4.2+)
    └── ECDH P-256 协商 DHKey → 直接生成 LTK

Secure Connections 更安全(防中间人),CTKD 必须基于 SC。

2.2 配对三阶段

Phase 1:配对能力协商(Pairing Feature Exchange)
复制代码
Central (手机)                    Peripheral (耳机)
     |                                    |
     |--- Pairing Request (Code 0x01) --->|
     |    io_cap / auth_req / key_dist    |
     |                                    |
     |<-- Pairing Response (Code 0x02) ---|
     |    io_cap / auth_req / key_dist    |

关键字段(对应代码 app_ble.c:3836):

  • auth_req:认证需求位域
    • SMP_AUTH_SC_SUPPORT (0x08):要求 Secure Connections
    • SMP_AUTH_MITM_PROTECT (0x04):要求 MITM 防护(CTKD 必须置位)
    • SMP_AUTH_BONDING (0x01):要求绑定(保存密钥)
  • init_key_dist / resp_key_dist:密钥分发位域
    • GAP_KDIST_ENCKEY (0x01):分发 LTK+EDIV+Rand
    • GAP_KDIST_IDKEY (0x02):分发 IRK+Identity Address
    • GAP_KDIST_SIGNKEY(0x04):分发 CSRK
    • GAP_KDIST_LINKKEY(0x08)分发 BR/EDR Link Key(CTKD 专用)
Phase 2:密钥生成(Authentication)

取决于双方 IO Capability 决定的 Association Model:

IO Cap 组合 Association Model MITM 防护
NoInputNoOutput + NoInputNoOutput Just Works
DisplayOnly + KeyboardOnly Passkey Entry
DisplayYesNo + DisplayYesNo Numeric Comparison
任意一方有 OOB 数据 OOB

对于 CTKD 场景(app_ble.c:3839-3840):

c 复制代码
p_requirements->auth_req |= SMP_AUTH_SC_SUPPORT;   // 强制 SC
p_requirements->auth_req |= SMP_AUTH_MITM_PROTECT; // 强制 MITM

耳机强制要求 SC + MITM,手机侧无 IO Capability 时降级为 Just Works(无 MITM),但 SC 仍保持。

SC Pairing Phase 2 完整流程(Spec Vol 3 Part H § 2.3.5.6)

复制代码
Central (手机/Initiator)              Peripheral (耳机/Responder)
         |                                        |
         |====== 步骤 1:公钥交换 ===============|
         |--- Pairing Public Key (0x0C) Pkx,Pky->|  Initiator 的 ECDH P-256 公钥
         |<-- Pairing Public Key (0x0C) Pkx,Pky--|  Responder 的 ECDH P-256 公钥
         |                                        |
         |    双方用对方公钥计算:                |
         |    DHKey = ECDH(私钥, 对方公钥)        |  共享 256-bit 秘密
         |                                        |
         |====== 步骤 2:Commitment(f4 函数)===|
         |    (Just Works / Numeric Comparison)  |
         |--- Pairing Confirm (0x03) Ca ---------->|  Ca = f4(PKax, PKbx, Na, 0)
         |<-- Pairing Confirm (0x03) Cb -----------|  Cb = f4(PKbx, PKax, Nb, 0)
         |                                        |
         |====== 步骤 3:随机数交换 ==============|
         |--- Pairing Random (0x04)  Na ---------->|  Initiator 揭示随机数 Na
         |<-- Pairing Random (0x04)  Nb -----------|  Responder 揭示随机数 Nb
         |                                        |
         |    双方验证 Confirm 值:               |
         |    Responder 验证:Ca == f4(PKax, PKbx, Na, 0) ?
         |    Initiator 验证:Cb == f4(PKbx, PKax, Nb, 0) ?
         |                                        |
         |====== 步骤 4:LTK 派生(f5 函数)====|
         |    MacKey, LTK = f5(DHKey, Na, Nb, A_init, A_resp)
         |    双方独立计算,结果相同              |
         |                                        |
         |====== 步骤 5:DHKey Check(f6 函数)=|
         |--- Pairing DHKey Check (0x0D) Ea ----->|  Ea = f6(MacKey, Na, Nb, rb, IOcap_a, A_init, A_resp)
         |<-- Pairing DHKey Check (0x0D) Eb ------|  Eb = f6(MacKey, Nb, Na, ra, IOcap_b, A_resp, A_init)
         |                                        |
         |    Responder 验证 Ea:防止中间人攻击   |
         |    Initiator 验证 Eb:防止中间人攻击   |
         |    DHKey Check 通过 → SMP Phase 2 完成 |

三个核心密码学函数(Spec Vol 3 Part H § 2.2.6--2.2.8):

复制代码
f4(U, V, X, Z) = AES-CMAC_X(U || V || Z)
  用途:生成 Commitment 值(Confirm PDU 的内容)
  U, V = 各自 ECDH 公钥的 X 坐标(256-bit)
  X    = 随机数(128-bit Nonce)
  Z    = Association Model 标识(Just Works=0x00,Passkey=当前bit位)

f5(W, N1, N2, A1, A2) → { MacKey, LTK }
  用途:从 DHKey 派生 MacKey 和 LTK(128-bit × 2)
  W    = DHKey(256-bit ECDH 共享密钥)
  实现:双 AES-CMAC,counter=0 出 MacKey,counter=1 出 LTK

f6(W, N1, N2, R, IOcap, A1, A2) = AES-CMAC_W(N1 || N2 || R || IOcap || A1 || A2)
  用途:生成 DHKey Check 值(防中间人)
  W    = MacKey(f5 的输出)
  R    = OOB 随机数 / Passkey Confirm 数据

安全性关键:f6 的设计保证了只有真正持有 DHKey 的一方才能生成正确的 Check 值,若有中间人劫持了 ECDH 交换,其 DHKey 不同,f6 验证必然失败。

Phase 3:密钥分发(Key Distribution)
复制代码
Central (手机)                    Peripheral (耳机)
     |                                    |
     |<-- Encryption Information (LTK) ---|  ← 耳机发给手机的 LTK
     |<-- Central Identification (EDIV+Rand)
     |<-- Identity Information (IRK) -----|
     |<-- Identity Address Information ---|
     |                                    |
     |--- Encryption Information (LTK) -->|  ← 手机发给耳机的 LTK
     |--- Central Identification -------->|
     |--- Identity Information (IRK) ---->|
     |--- Identity Address Information -->|
     |--- (如果 CTKD) Link Key ---------->|  ← CTKD 扩展:BR/EDR Link Key

2.3 LL 加密过程(Spec Vol 6 Part B § 5.1.3)

SMP 配对完成(或重连)后,链路层(Link Layer)发起 LL Encryption Procedure 启动 AES-CCM 加密。这是 LTK 实际"被使用"的时刻。

PDU 交互流程
复制代码
Central (手机)                         Peripheral (耳机)
         |                                        |
         |--- LL_ENC_REQ ----------------------->|
         |    Rand[64]                            |  SC 模式下为全0
         |    EDIV[16]                            |  SC 模式下为全0
         |    SKDm[64]  (Session Key Diversifier, 手机半部分)
         |    IVm[32]   (Initialization Vector, 手机半部分)
         |                                        |
         |                耳机执行:              |
         |                LTK 查找:              |
         |                nv_record_ble_record_find_ltk(addr, ltk, ediv)
         |                → RPA 先解析为身份地址 |
         |                → 匹配 EDIV+Rand 找 LTK |
         |                                        |
         |<-- LL_ENC_RSP ------------------------|
         |    SKDs[64]  (Session Key Diversifier, 耳机半部分)
         |    IVs[32]   (Initialization Vector, 耳机半部分)
         |                                        |
         |    双方计算会话密钥 SK:               |
         |    SKD = SKDm || SKDs  (128-bit 拼接)|
         |    SK  = AES-128(LTK, SKD)             |
         |    IV  = IVm || IVs    (64-bit 拼接) |
         |                                        |
         |<-- LL_START_ENC_REQ ------------------|  Peripheral 先启用加密
         |                                        |
         |--- LL_START_ENC_RSP ----------------->|  Central 确认加密
         |                                        |
         |    链路加密完成(AES-CCM with SK)     |
         |    触发 GAP_CONN_EVENT_ENCRYPTED        |
         |    ENCT log:error_code=0              |

关键字段说明

字段 长度 SC 模式 Legacy 模式
Rand 8 字节 全 0 Responder 生成的随机数(LTK 索引)
EDIV 2 字节 全 0 Responder 生成的随机标识(LTK 索引)
SKDm 8 字节 Central 随机生成 同左
SKDs 8 字节 Peripheral 随机生成 同左
IVm 4 字节 Central 随机生成 同左
IVs 4 字节 Peripheral 随机生成 同左

会话密钥 SK 推导

复制代码
SK = AES-128(LTK, SKDm || SKDs)
  • SKD 和 IV 每次连接随机生成(保证每次会话密钥不同)
  • LTK 是长期密钥 ,SK 是临时会话密钥------LTK 不直接用于加解密数据
  • AES-CCM 用 SK 和 IV 对所有 LL Data PDU 做认证加密(提供完整性保护)
LTK 查找失败的后果

若耳机 NV 找不到 EDIV+Rand 对应的 LTK(nv_record_ble_record_find_ltk 返回 false):

c 复制代码
// app_ble.c:1194 app_ble_reply_peer_ltk_request
if (!ltk_found) {
    gap_reply_peer_ltk_request(connhdl,
                               true,   // negative_reply = true
                               NULL);  // 无 LTK
}

Peripheral 发送 LL_REJECT_IND(错误码 0x06 = Pin or Key Missing),链路加密失败,触发 ENCT 事件且 error_code != 0SMP_ERROR_ENABLE_ENC_FAILED = 0x16 或协议错误)。

LTK 存在但 MAC 验证失败

若 NV 中存有 LTK,但 LTK 内容不正确(iOS/Android"脏 LTK"场景):

  • 耳机提交 LTK,会话密钥 SK 被双方各自计算
  • 双方 SK 不同(因 LTK 不同)
  • AES-CCM 消息认证失败:BT_HCI_ERR_CONN_TERM_MIC_FAILURE (0x3E)
  • 对应 CO_LOG:CNNE ... 16(0x16 = MIC failure)

三、CTKD 原理详解

3.1 CTKD 是什么

CTKD(Cross-Transport Key Derivation,跨传输密钥派生) 是 Bluetooth Core Spec 5.1 引入的机制(在 4.2 时已有雏形),允许在一种传输(BLE 或 BR/EDR)完成配对后,派生出另一种传输所需的密钥,从而实现"配对一次,两个传输都加密"。

复制代码
两个方向:
① CTKD over LE(LE 配对 → 派生 BR/EDR Link Key)  ← 本项目主要场景
② CTKD over BR/EDR(BR/EDR 配对 → 派生 BLE LTK)

代码中对应两个 NV 写入路径:

  • BT_NV_REC_ADD_CTKD_OVER_LEapp_ble.c:1704):BLE 配对派生出 BR/EDR 记录
  • BLE_NV_REC_ADD_CTKD_OVER_BREDRapp_ble.c:3980):BR/EDR 配对派生出 BLE 记录

3.2 为什么需要 CTKD

传统双模设备(BLE + BR/EDR)用户需要:

  1. 手机通过 BLE 扫描并配对耳机(SMP)
  2. 手机再通过 BR/EDR 配对耳机(HFP/A2DP 连接时的 PIN/SSP)

两次独立配对,用户体验割裂,且安全性参差不齐。

CTKD 后:

  • 只需 BLE 配对一次(SMP SC + MITM)
  • 自动派生 BR/EDR Link Key,BR/EDR 连接时直接使用
  • 用户无感知

3.3 CTKD over LE 的密钥派生算法

基于 AES-CMAC 函数,使用 BLE SC Pairing 产生的 BR/EDR Link Key 从 LTK 派生:

复制代码
h6(W, keyID) = AES-CMAC_W(keyID)
h7(SALT, W)  = AES-CMAC_SALT(W)

// 派生步骤(Spec Section 2.4.2.4)
ILK = h7("LEBR", LTK)          // Intermediate Link Key(如果 SC)
LinkKey = h6(ILK, "lesc")       // 最终 BR/EDR Link Key

关键前提:BLE 配对必须使用 Secure Connections(SC),Legacy Pairing 不支持 CTKD。

3.4 Key Distribution 中的 LinkKey 标志位

SMP Pairing Request/Response 的 key_dist 字段中 bit3(0x08)= LinkKey,双方都置位时表示"我要分发派生的 BR/EDR Link Key"。

代码(app_ble.c:3850-3853):

c 复制代码
if (key_dist & GAP_KDIST_LINKKEY) {
    p_requirements->init_key_dist |= GAP_KDIST_LINKKEY;  // Initiator 分发
    p_requirements->resp_key_dist |= GAP_KDIST_LINKKEY;  // Responder 分发
}

这就是 [SMP_REQUIRE] 日志里的 ikeyrkey 字段(app_ble.c:1821)。


四、完整连接交互流程

4.1 首次配对(CTKD over LE)时序图

复制代码
手机 (Central/Initiator)               耳机 (Peripheral/Responder)
         |                                        |
         |====== BLE 广播扫描 ===================>|
         |<----- ADV_IND / ADV_EXT_IND -----------|  FADV/SADV
         |                                        |
         |====== BLE 连接建立 ===================>|
         |------ LL_CONNECT_REQ --------------->  |
         |<----- [GAP_EVENT] event=8240 --------- |  connhdl 分配
         |                                        |
         |====== MTU 协商 ========================|
         |------ ATT_EXCHANGE_MTU_REQ ----------->|
         |<----- ATT_EXCHANGE_MTU_RSP ------------|  [GAP_EVENT] event=8244
         |                 (MTU: 23→512)          |
         |                                        |
         |====== SMP Phase 1:配对能力协商 =======|
         |------ Pairing Request (0x01) --------->|  触发 [SMP_REQUIRE] 日志
         |       auth=SC+MITM+Bond                |  ikey=0x0F rkey=0x0F (含0x08 LinkKey)
         |<----- Pairing Response (0x02) ---------|
         |       auth=SC+MITM+Bond                |
         |                                        |
         |====== SMP Phase 2:SC Authentication ==|
         |------ Pairing Public Key (0x0C) ------>|
         |<----- Pairing Public Key (0x0C) -------|
         |       (ECDH P-256 公钥交换)            |
         |------ Pairing Confirm (0x03) --------->|  (Just Works 场景)
         |<----- Pairing Confirm (0x03) -----------|
         |------ Pairing Random (0x04) ---------->|
         |<----- Pairing Random (0x04) -----------|
         |       双方各自计算 DHKey→LTK           |
         |------ Pairing DHKey Check (0x0D) ----->|
         |<----- Pairing DHKey Check (0x0D) ------|
         |                                        |
         |====== LL 加密启动 =====================|
         |------ LL_ENC_REQ (EDIV+Rand) --------->|  触发 GAP_USER_LTK_REQUEST_CONFIRM
         |<----- LL_ENC_RSP ----------------------|  耳机查 NV → app_ble_reply_peer_ltk_request
         |<----- LL_START_ENC_REQ ----------------|
         |------ LL_START_ENC_RSP ---------------- |
         |       [GAP_EVENT] event=8245 (ENCT)    |  链路加密完成
         |                                        |
         |====== SMP Phase 3:密钥分发 ===========|
         |<----- Encryption Information (LTK) ----|
         |<----- Identity Information (IRK) ------|
         |<----- Identity Address Information ----|
         |<----- (CTKD) LinkKey PDU (0x0B) -------|  ← CTKD 关键步骤:BR/EDR Key 分发
         |------ Encryption Information (LTK) --->|  触发 GAP_CONN_EVENT_RECV_KEY_DIST
         |------ Identity Information (IRK) ----->|
         |------ Identity Address Information ---->|
         |------ (CTKD) LinkKey PDU (0x0B) ------->|
         |       GAP_RECV_DERIVED_BT_LINK_KEY      |  触发 RLTK 日志
         |       BT_NV_REC_ADD_CTKD_OVER_LE 写入  |  BR/EDR 配对记录存 NV
         |                                        |
         |       [SMP_PAIR_CMP] err=0x00          |  配对完成
         |                                        |
         |====== GATT 数据通道建立 ===============|
         |       app_datapath_server_connected     |
         |                                        |
         |====== 连接参数更新 ====================|
         |<----- LL_CONNECTION_PARAM_REQ ---------|  [GAP_EVENT] event=8248
         |------ LL_CONNECTION_PARAM_RSP -------->|  [GAP_EVENT] event=8249
         |       (interval=180ms, timeout=5s)     |
         |                                        |
         |====== BR/EDR 连接(之后) =============|
         |       手机发起 BR/EDR 连接             |
         |       耳机用 CTKD 派生的 Link Key      |
         |       直接完成 BR/EDR 认证,无需再次配对|

4.2 已配对设备重连(无 SMP)时序图

复制代码
手机 (Central)                         耳机 (Peripheral)
         |                                        |
         |====== BLE 连接建立 ===================>|
         |       [GAP_EVENT] event=8240           |  connhdl=3
         |                                        |
         |====== MTU 协商 ========================|
         |       [GAP_EVENT] event=8244           |  MTU→512
         |                                        |
         |====== LL 加密(无 SMP!)=============|
         |------ LL_ENC_REQ (EDIV+Rand) --------->|  用已存的 EDIV+Rand 查 LTK
         |       GAP_USER_LTK_REQUEST_CONFIRM      |
         |       nv_record_ble_record_find_ltk()  |  从 NV 查 LTK
         |<----- LL_ENC_RSP ----------------------|
         |<----- LL_START_ENC_REQ ----------------|
         |------ LL_START_ENC_RSP ---------------- |
         |       ENCT 事件(无 PAIRING 标志)     |  ← 这里不触发 BOND 事件
         |                                        |
         |====== GATT 数据通道 ===================|
         |       app_datapath_server_connected     |
         |       box cmd: 510c / 5108 / b01       |

这就是本次安卓正常连接日志的完整路径:无 SMP,直接 LTK 加密


五、iOS vs Android 差异

5.1 SMP 配对行为差异

行为 Android iOS
首次配对发起方 Central(手机主动) Central(手机主动)
SC 支持 支持(Android 6.0+) 支持(iOS 9+)
MITM 要求 默认 Just Works(无 MITM) 倾向于 Numeric Comparison / Just Works
CTKD LinkKey 分发 支持(Android 12+ 标准行为) 不支持(iOS 不分发 LinkKey PDU)
重连加密方式 LL_ENC_REQ 用 EDIV+Rand(Legacy 或 SC) 同左,但 SC 下用 Rand=0 + EDIV=0
配对信息丢失处理 收到 LL_ENC_REJ 后重新配对 弹出系统配对对话框重新配对
RPA(随机地址) 支持,但部分厂商用公开地址 强制使用 RPA,需要 IRK 解析

5.2 CTKD 支持差异(核心)

复制代码
Android (12+)
  BLE 配对 ──→ SMP SC Pairing
                    ↓
              LinkKey PDU 分发(0x08 in key_dist)
                    ↓
              耳机存入 BR/EDR NV(BT_NV_REC_ADD_CTKD_OVER_LE)
                    ↓
              BR/EDR 连接时直接用 Link Key 认证 ✓

iOS (所有版本)
  BLE 配对 ──→ SMP SC Pairing
                    ↓
              LinkKey PDU 不分发(key_dist 无 0x08)
                    ↓
              BR/EDR 连接时需要独立 SSP 配对(弹配对确认框)

实际影响

  • Android 用户:BLE 配对后,BR/EDR 连接无感知自动完成
  • iOS 用户:BLE 配对后,BR/EDR 仍需独立配对(或通过其他机制如 MFi)

5.3 RPA 处理差异

iOS 强制使用 RPA(Resolvable Private Address),每次重连地址可能不同。耳机需要:

  1. SMP Phase 3 获取 iOS 的 IRK
  2. 将 IRK 加入 Resolving List(app_ble_add_devices_info_to_resolving()
  3. 重连时用 IRK 解析 RPA,找到对应的 LTK(app_ble_continue_recv_peer_ltk_req()

代码路径(app_ble.c:1233-1249):

c 复制代码
// RPA 需要先解析
if (conn->peer_type == BT_ADDR_TYPE_RANDOM) {
    gap_resolve_rpa(&conn->peer_addr,
                    app_ble_continue_recv_peer_ltk_req, ...)
}

Android 部分机型使用公开地址,不需要 RPA 解析,流程更简单。

5.4 连接参数协商差异

参数 Android 典型值 iOS 典型值
初始 Connection Interval 7.5ms ~ 45ms 30ms ~ 90ms
Peripheral Latency 0 0
协商后 Interval 按设备请求(本例 180ms) 保守,通常 ≥ 20ms
MTU 协商 快速升到 512 升到 185 或 512(iOS 版本相关)

六、本项目代码关键路径

6.1 事件流 → 日志对应表

GAP 事件 event 值 CO_LOG tag TRACE tag 触发时机
GAP_CONN_EVENT_OPENED 0x2030 (8240) CNNS --- 连接建立
GAP_CONN_EVENT_MTU_CHANGED 0x2034 (8244) MTUE --- MTU 协商
GAP_CONN_EVENT_USER_CONFIRM --- USRC --- 数字比对确认
GAP_CONN_EVENT_ENCRYPTED 0x2035 (8245) ENCT --- 链路加密完成
GAP_CONN_EVENT_RECV_SMP_REQUIRE --- PARQ [SMP_REQUIRE] 收到配对请求
GAP_CONN_EVENT_PAIRING_COMPLETE --- PACM [SMP_PAIR_CMP] 配对完成
GAP_CONN_EVENT_RECV_KEY_DIST --- KEYD --- 收到密钥分发
GAP_CONN_EVENT_UPDATE_REQ 0x2038 (8248) UPRQ --- 连接参数更新请求
GAP_CONN_EVENT_PARAMS_UPDATE 0x2039 (8249) UPED --- 连接参数更新完成
GAP_EVENT_RECV_DERIVED_BLE_LTK --- RLTK --- BR/EDR 派生 BLE LTK
GAP_USER_LTK_REQUEST_CONFIRM --- --- --- 对端请求 LTK

6.2 CTKD 核心函数调用链

复制代码
SMP Phase 3 收到 LinkKey PDU
  └── GAP_CONN_EVENT_RECV_KEY_DIST
        └── key_dist->key_type == GAP_RECV_DERIVED_BT_LINK_KEY
              └── bluetooth_nv_mgr_bt_record_add(BT_NV_REC_ADD_CTKD_OVER_LE, &record)
                    └── BR/EDR Link Key 存入 NV,供经典蓝牙连接使用

重连时收到 LTK 请求
  └── GAP_USER_LTK_REQUEST_CONFIRM
        └── app_ble_ltk_request_recv_handler(conn, ediv)
              ├── [if RPA] gap_resolve_rpa() → app_ble_continue_recv_peer_ltk_req()
              └── app_ble_reply_peer_ltk_request(conn, ediv)
                    └── nv_record_ble_record_find_ltk(addr, ltk, ediv)
                          └── gap_reply_peer_ltk_request(connhdl, false, ltk)

七、排错指南

7.1 问题:BLE 连接成功,但看不到 [SMP_REQUIRE]

可能原因

原因 判断方法 处置
① 已配对重连,正常行为 检查是否有 ENCT 事件 + smp_pairing_ongoing=0 正常,无需处理
__CTKD_ENABLE__ 未定义 检查有无 [GAP_EVENT] 打印 重新确认编译宏
③ SMP 被对端跳过 检查 ENCT 之后有无 PACM 检查手机侧行为

7.2 问题:[SMP_PAIR_CMP] err != 0

常见错误码(SMP Error Code,Spec Table 3.7):

err_code 名称 含义 常见原因
0x01 Passkey Entry Failed 密码输入失败 用户取消或超时
0x02 OOB Not Available OOB 数据不可用 OOB 配置错误
0x03 Authentication Requirements 认证需求不满足 双方 auth_req 不兼容
0x05 Pairing Not Supported 不支持配对 被拒绝
0x06 Encryption Key Size 加密密钥长度不足 最小密钥长度配置问题
0x07 Command Not Supported 命令不支持 SC 协商失败降级
0x08 Unspecified Reason 未指定原因 各种软件问题
0x09 Repeated Attempts 重复尝试 连续配对失败
0x0A Invalid Parameters 参数无效 协议格式错误
0x0B DHKey Check Failed DHKey 校验失败 MITM 攻击或密钥不匹配
0x0C Numeric Comparison Failed 数字比对失败 用户拒绝确认

7.3 问题:CTKD 派生失败(BR/EDR 仍需配对)

排查步骤:

  1. 检查 [SMP_REQUIRE] 中的 ikey/rkey 是否包含 0x08(LinkKey 位)
  2. 检查 KEYD CO_LOG 是否有 key_type=GAP_RECV_DERIVED_BT_LINK_KEY 的记录
  3. 检查 RLTK 日志是否出现
  4. 确认手机是否支持 CTKD(Android 12+ 支持,iOS 不支持)
  5. 检查 BLE 配对是否使用了 SC(bond_info_bfBONDED_SECURE_CONNECTION 位)

7.4 问题:重连时 LTK 查找失败(加密失败)

log 特征:ENCT 事件出现且 error_code != 0,具体是 SMP_ERROR_ENABLE_ENC_FAILED

代码路径(app_ble.c:1598-1603):

c 复制代码
if (SMP_ERROR_ENABLE_ENC_FAILED == encrpt->error_code && conn->conn_flag.is_central == true) {
    // 删除失败的 NV 记录
    bluetooth_nv_mgr_ble_record_del(BLE_NV_REC_DEL_LE_ENC_FAILURE, ...);
    // 重新触发 SMP 配对
    gap_start_authentication(conn->connhdl, GAP_AUTH_STARTED_BY_UPPER_APP);
}

意味着:LTK 不匹配时固件会自动清除旧记录并重新配对,用户侧可能看到配对弹框。

7.5 问题:iOS 连接正常但 Android 连接异常

对比要点:

检查项 Android log 预期 iOS log 预期
广播地址类型 公开地址或 RPA 必定 RPA
首次配对 [SMP_REQUIRE] ikey/rkey 含 0x08 ikey/rkey 无 0x08
KEYD key_type GAP_RECV_DERIVED_BT_LINK_KEY 无此类型
重连有无 [SMP_REQUIRE] 无(直接 LTK) 无(直接 LTK)
ENCT error_code 0(成功) 0(成功)

7.6 关键 log 速查

复制代码
# 连接建立
[GAP_EVENT] connhdl=X event=8240

# 首次配对开始(有 SMP_REQUIRE → 首次配对)
[SMP_REQUIRE] ... ikey=0x0F rkey=0x0F    ← ikey/rkey 含 0x08 = CTKD

# 链路加密完成(重连或首次均有)
ENCT ... error_code=0

# CTKD 密钥分发完成
RLTK  ltk_generated_but_still_wait_peer_kdist=0

# 配对完成(首次配对有)
[SMP_PAIR_CMP] err=0x00

# GATT 数据通道建立
app_datapath_server_connected.[GATT/BLE] conidx:XX connhdl:X

7.7 iOS MIC Failure(CNNE 0x16):脏 LTK 死循环实例分析

背景:此案例来自真实 iOS 设备连接耳机的 log(约 73000 行)。

症状 :iOS 反复与耳机配对,每次配对成功(PACM err=0)后 iOS 主动断开(CNNE 13),重连时触发 MIC 故障(CNNE 16),最终 SMP 配对失败(PARF 08)。

日志模式解析

正常配对成功后的意外断开(反复循环)

复制代码
# iOS 完成 SMP 配对,配对成功
PARQ 0401          ← Pairing Request queued, conidx=4
PARS ...           ← Pairing Response stored
...
ENCR 0101          ← 加密成功(error=0)
ENCT 31010004 00   ← ENCRYPTED 事件,conhdl=0x31010004,err=0
...
KEYD               ← Key Distribution 开始
...
PACM 04            ← Pairing Complete, conidx=4, err=0  ← 配对成功!

# iOS 随即主动断开(0x13 = Remote User Terminated Connection)
CNNE 31010004 13   ← 断开,conidx=4,reason=0x13

0x13 表示 iOS 主动发起断开(非故障),可能原因:iOS 内部系统行为(配对完成后触发 re-bond 流程)。

脏 LTK 触发 MIC Failure

复制代码
# iOS 重新连接,用刚配对存储的(已失效的)LTK 尝试加密
CNNS 31010003      ← 新连接 conidx=3 建立
...
LL_ENC_REQ         ← iOS 发送 EDIV+Rand,耳机查 NV 找到 LTK
LL_ENC_RSP         ← 耳机回复,双方各自计算 SK = AES-128(LTK, SKD)
                      ← 由于 LTK 不一致,SK 不同 → AES-CCM MAC 验证失败

CNNE 31010004 16   ← 断开,reason=0x16 = BT_HCI_ERR_CONN_TERM_MIC_FAILURE

第二次也出现同样错误:

复制代码
CNNE 30010003 16   ← 连接断开,reason=0x16(MIC failure 再次)

最终 SMP 配对失败(11:02:24)

复制代码
PARQ 0401          ← iOS 再次发起配对
...
PARF 04 08         ← Pairing Failed, conidx=4, reason=0x08 (Unspecified Reason)
PARE 04 00 09      ← Pairing Error, err=0x09 (Repeated Attempts)
ENCT 31010004 08   ← 加密事件,err=0x08
PACM 04            ← Pairing Complete, 但已失败

# 代码 app_custom.c:198-203 触发:
#   app_ble_disconnect()  ← 主动断开
#   app_ble_refresh_adv_state_generic()  ← 刷新广播

0x09 = Repeated Attempts:SMP 规范(Spec Table 3.7)规定,短时间内多次配对失败后必须拒绝新的配对请求(防止暴力破解),此错误表示耳机触发了速率限制。

根因分析
复制代码
iOS 内部 re-bond 机制(推测)
        ↓
配对成功 → iOS 立即断开(reason=0x13)
        ↓
iOS 内部旧 LTK 未正确更新或版本不一致
        ↓
重连时用旧 EDIV+Rand 发 LL_ENC_REQ
        ↓
耳机 NV 查到 LTK(可能是前一轮的)
        ↓
双方 SK 不同 → AES-CCM MAC 失败
        ↓
CNNE 0x16(MIC failure)
        ↓
iOS 再次触发 SMP Pairing
        ↓
多次重复后触发 Repeated Attempts (0x09)
        ↓
PARF 08(Unspecified)→ 最终连接失败
排查 checklist
检查项 预期(正常) 异常信号
SMP 后 iOS 是否断开 CNNE 13 后无再连接 CNNE 13 后立即重连 → iOS re-bond
重连加密 ENCT ... 00(成功) CNNE ... 16 → MIC failure
多次 SMP 后错误码 0x08(偶发) 0x09(Repeated Attempts)→ 速率限制触发
广播恢复 失败后重新可见 广播未刷新 → iOS 扫描不到耳机
解决方案
  1. iOS 端设置 → 蓝牙 → 忘记此设备,清除所有配对缓存,重新配对
  2. 耳机端:长按复位清除 NV 中的所有 BLE 配对记录
  3. 代码层面 :参见 8.3 方法 3 --- Peripheral 侧检测 MIC failure 后主动清除本地 LTK 并请求重配对

八、iOS SMP 配对失败后的恢复分析

场景:iOS 首次连接耳机,BLE SMP 配对失败,后续如何恢复并建立正常连接(含 CTKD)

8.1 配对失败时耳机端发生了什么

代码路径 app_custom.c:198-203

c 复制代码
if (err_code != 0) {
    // 主动断开 BLE 连接
    app_ble_disconnect(gap_zero_based_ble_conidx_as_hdl(conidx));
    // 刷新广播,重新可被发现
    app_ble_refresh_adv_state_generic();
}

SMP 配对失败后耳机自动执行:

  1. 主动断开 BLE 连接
  2. 重新刷新广播(重新进入可被扫描状态)

NV 状态 :SMP Phase 3(密钥分发)只在 Phase 2 认证成功后才执行,配对失败意味着 Phase 2 未完成,因此 NV 里不会写入任何 LTK / IRK 记录,耳机侧是干净的。


8.2 iOS 下次重连会发生什么

iOS 同样没有配对记录(配对失败),理论上下次连接时 iOS 应重新发起 SMP Pairing Request。

但 iOS 可能残留脏记录,表现为:

复制代码
iOS                                   耳机 (Peripheral)
  |                                        |
  |====== BLE 重新连接 ===================>|
  |                                        |
  |====== iOS 认为已配对,发加密请求 =====|
  |------ LL_ENC_REQ (脏 EDIV+Rand) ----->|
  |       nv_record_ble_record_find_ltk()  |  ← NV 查不到 LTK
  |       positive_reply = false           |
  |<----- LL_ENC_REJ (SMP_ERROR_ENABLE_ENC_FAILED)
  |                                        |
  |       ENCT 事件,error_code != 0      |
  |       is_central == false (耳机是 Peripheral)
  |       → 恢复逻辑不触发!              |  ← 代码 app_ble.c:1598 只处理 Central
  |       BLE_CONNECT_BOND_FAIL_EVENT      |
  |       连接断开                        |

根源在 app_ble.c:1598

c 复制代码
// 仅对 Central(手机主动)做自动恢复,Peripheral(耳机)不处理
if (SMP_ERROR_ENABLE_ENC_FAILED == encrpt->error_code && conn->conn_flag.is_central == true)
{
    bluetooth_nv_mgr_ble_record_del(BLE_NV_REC_DEL_LE_ENC_FAILURE, ...);
    gap_start_authentication(conn->connhdl, GAP_AUTH_STARTED_BY_UPPER_APP);
}
// is_central == false 时:无任何恢复动作,直接上报 BOND_FAIL 并断开

8.3 恢复方法

方法 1:iOS 手动忘记设备(推荐)
复制代码
iPhone → 设置 → 蓝牙 → 找到耳机 → 点"忘记此设备"
  • iOS 清除自己的脏配对记录
  • 重新扫描发现耳机
  • 走完整 SMP 首次配对流程
  • 适用场景:耳机 NV 干净,仅 iOS 侧有脏记录
方法 2:耳机出厂复位
  • 触发耳机长按复位,清除所有配对 NV 记录
  • 重新广播,iOS 重新发现并配对
  • 适用场景:双端均可能有脏记录,彻底清干净
方法 3(代码层面):Peripheral 侧加自动恢复逻辑

当前代码只对 Central 做自动恢复,可在 Peripheral 分支补充:

c 复制代码
// app_ble.c GAP_CONN_EVENT_ENCRYPTED 处理中
if (SMP_ERROR_ENABLE_ENC_FAILED == encrpt->error_code && conn->conn_flag.is_central == true)
{
    // 已有逻辑:Central 自动恢复
    bluetooth_nv_mgr_ble_record_del(BLE_NV_REC_DEL_LE_ENC_FAILURE, ...);
    gap_start_authentication(conn->connhdl, GAP_AUTH_STARTED_BY_UPPER_APP);
}
else if (SMP_ERROR_ENABLE_ENC_FAILED == encrpt->error_code && conn->conn_flag.is_central == false)
{
    // 新增:Peripheral 侧也清除失效记录,触发对端重新配对
    bluetooth_nv_mgr_ble_record_del(BLE_NV_REC_DEL_LE_ENC_FAILURE, (uint8_t *)&conn->sec.peer_addr);
    gap_start_authentication(conn->connhdl, GAP_AUTH_STARTED_BY_UPPER_APP);
}

注意:iOS 收到来自 Peripheral 的重配对请求行为因系统版本而异,可能弹对话框或直接接受,需实测验证。


8.4 CTKD 与 iOS 配对失败的关系

核心结论

复制代码
CTKD 的前提 = BLE SMP 配对成功
     ↓
iOS SMP 配对失败 = CTKD 无法进行
     ↓
且 iOS 本身不支持 CTKD(不分发 LinkKey PDU)
     ↓
iOS 的 BR/EDR 连接走独立 SSP 配对,与 BLE CTKD 无关

CTKD 完整成功的标志(必须全部出现):

步骤 log 标志 说明
SMP Phase 2 完成 [SMP_PAIR_CMP] err=0x00 配对无错误
密钥分发收到 LinkKey KEYDGAP_RECV_DERIVED_BT_LINK_KEY 仅 Android 会分发
BLE LTK 派生完成 RLTK ... wait=0 等待对端 IRK 完成
BR/EDR 记录写入 NV BT_NV_REC_ADD_CTKD_OVER_LE 供经典蓝牙连接使用

iOS 的正常连接路径(不含 CTKD):

复制代码
iOS BLE 配对成功(SMP SC, 无 LinkKey 分发)
    ↓
耳机存入 BLE LTK / IRK(BLE 侧)
    ↓
iOS 发起 BR/EDR 连接(HFP/A2DP)
    ↓
独立 SSP Numeric Comparison 或 Just Works 配对
    ↓
BR/EDR Link Key 独立存入 NV(与 BLE CTKD 无关)

九、蓝牙协议参考

9.1 Spec 章节索引

规范 相关章节 内容
Core Spec 5.4 Vol 3 Part H § 2 SMP 完整规范
Core Spec 5.4 Vol 3 Part H § 2.2.6 f4 函数:Commitment 生成(AES-CMAC)
Core Spec 5.4 Vol 3 Part H § 2.2.7 f6 函数:DHKey Check 验证(AES-CMAC)
Core Spec 5.4 Vol 3 Part H § 2.2.8 f5 函数:MacKey 和 LTK 派生(双 AES-CMAC)
Core Spec 5.4 Vol 3 Part H § 2.3 Pairing Methods(Just Works / Passkey / OOB / Numeric)
Core Spec 5.4 Vol 3 Part H § 2.3.5.1 Legacy Pairing:LTK 随机生成 + STK 保护分发
Core Spec 5.4 Vol 3 Part H § 2.3.5.6 SC Pairing Phase 2:公钥交换 → DHKey → f5 派生
Core Spec 5.4 Vol 3 Part H § 2.4 Cryptographic Toolbox(h6/h7/f4/f5/f6)
Core Spec 5.4 Vol 3 Part H § 2.4.2.4 CTKD 密钥派生算法(h6/h7 + LTK → Link Key)
Core Spec 5.4 Vol 3 Part H § 3.6.11 Pairing Request/Response PDU 格式
Core Spec 5.4 Vol 2 Part E § 7.7.65.29 HCI LE Long Term Key Request Event
Core Spec 5.4 Vol 6 Part B § 5.1.3 LL Encryption Procedure(LL_ENC_REQ/RSP/START_ENC)
Core Spec 5.4 Vol 6 Part B § 5.1.3.1 SK = AES-128(LTK, SKD) 会话密钥推导
Core Spec 5.4 Vol 3 Part H Table 3.7 SMP Error Code 表(包含 0x09 Repeated Attempts)

9.2 关键错误码速查

HCI/SMP 错误码 十六进制 含义 典型场景
BT_HCI_ERR_CONN_TERM_MIC_FAILURE 0x3E(HCI),CO_LOG 16 AES-CCM MAC 验证失败,LTK 不匹配 iOS/Android 脏 LTK 重连
BT_HCI_ERR_REMOTE_USER_TERM_CONN 0x13,CO_LOG 13 对端主动断开 iOS re-bond 后正常断开
SMP_ERROR_ENABLE_ENC_FAILED 0x16(SMP 层,不同于 HCI 0x3E LL 加密启动失败 NV 无对应 LTK
SMP Unspecified Reason 0x08 未指定 SMP 错误 多种软件问题
SMP Repeated Attempts 0x09 短时间内重复配对触发速率限制 iOS 反复配对循环后
SMP DHKey Check Failed 0x0B DHKey 验证(f6)失败 MITM 攻击或密钥不匹配

相关推荐
会编程的土豆1 小时前
数据库mysql八股
数据库·笔记·八股
数据皮皮侠AI1 小时前
退市监督数据(2000-2024)
大数据·人工智能·笔记·机器学习·回归
minglie12 小时前
zynq开发板的xvcServer作为烧录器
学习
微露清风2 小时前
快速排序算法学习记录
学习·算法·排序算法
AAA@峥3 小时前
系统化学习 MySQL:数据类型、库表管理、增删改查全解析
数据库·学习·mysql
神明不懂浪漫4 小时前
【第七章】Java中的常用类
java·开发语言·前端·经验分享·笔记
GeekArch15 小时前
第28讲:避坑——AI堆栈分配错误、栈溢出BUG
c语言·人工智能·stm32·mcu·学习·bug
weixin_4280053016 小时前
C#调用 AI学习从0开始-第3阶段RAG向量数据库-文档切分与入库第15天
人工智能·学习·c#·向量数据库·rag·qdrant·文档切分与入库
dankokoko18 小时前
一.函数与极限2
学习