BLE SMP 基础 —— 配对、密钥体系与安全等级
- 一、SMP 是什么
- 1.1 SMP 站在哪一层
- 1.2 三个威胁
- 1.3 术语说明
- 二、配对三阶段
- 2.1 总时序
- 2.2 Phase 1:配对特性交换
- 2.2.1 报文结构
- 2.2.2 AuthReq 位域(1 字节)
- 2.2.3 Key Distribution 位域(1 字节)
- 2.3 Phase 2:认证与密钥生成
- 2.3.1 Legacy 配对(第一代)
- 2.3.2 LE Secure Connections(LESC,第二代)
- 2.4 Phase 3:密钥分发
- 三、配对方式、密钥与隐私
- 3.1 四种配对方式
- 3.2 决策流程
- 3.3 Just Works 无法抵御中间人攻击的原因
- 3.4 秘钥体系
- 3.5 隐私与随机地址
- 四、安全等级与 ATT 权限
一、SMP 是什么
1.1 SMP 站在哪一层
应用层(配对回调 / 权限设置) ↑ ( SMP Security Manager Protocol ) ← 配对、密钥分发,本文重点 ↑ 基于 L2CAP(逻辑链路控制与适配协议,CID = 0x0006) ↑ 基于 Link Layer(链路层:真正执行加密的地方) ↑ 基于 Physical Layer(1M / 2M / Coded PHY)SMP 跑在 L2CAP CID
0x0006上,但 SMP 只负责 “把钥匙谈好”,真正加密是 Link Layer 干的。
1.2 三个威胁
BLE 空口是无线电 —— 等于在一个大广场上喊话。于是有三类攻击者:
| 编号 | 攻击者能力 | 攻击名称 | 后果 |
|---|---|---|---|
| ① | 只能被动接收 | 被动窃听 | 数据内容泄露 |
| ② | 能接收也能发送 | 中间人攻击(MITM) | 可解密 + 可篡改 + 可冒充 |
| ③ | 能接收,并长期记录地址 | 被动追踪 | 用户行踪被关联 |
这三个安全威胁各自对应一个需要被保护的安全属性,每个属性由一种机制实现,机制背后是一把具体的密钥:
| 威胁 | 安全属性 | 机制 | 具体技术 |
|---|---|---|---|
| ① 被动窃听 | 机密性 | 加密链路 | LTK |
| ② 中间人 | 真实性 | 认证 | 四种配对方式 |
| ③ 被动追踪 | 隐私 | 地址随机化(RPA) | IRK |
1.3 术语说明
本节提前给出若干术语的简要定义,这些术语将在后文中反复出现。
| 术语 | 全称 | 简要定义 |
|---|---|---|
| LTK | Long Term Key | 用于加密链路,可长期复用 |
| IRK | Identity Resolving Key | 用于解析对端随机地址,不参与加密 |
| CSRK | Connection Signature Resolving Key | 用于验证数据签名,不参与加密 |
| Legacy | LE Legacy Pairing | 第一代配对技术;以 6 位数字作为密钥种子,可被离线暴力破解 |
| LESC | LE Secure Connections | 第二代配对技术;采用 ECDH 密钥交换,无法被离线破解 |
| Just Works | — | 不进行任何验证,直接建立加密链路;无法抵御中间人攻击 |
| Passkey Entry | — | 一端显示 6 位数字,由另一端输入 |
| Numeric Comparison | — | 两端显示同一个 6 位数字,由用户比对确认 |
| OOB | Out Of Band | 通过 BLE 之外的通道(NFC / 二维码)交换验证信息 |
| RPA | Resolvable Private Address | 周期性变化的随机地址;持有对端 IRK 者可识别其身份 |
配对 vs 绑定:
- 配对(Pairing):双方交换特性、生成密钥、分发密钥的过程。
- 绑定(Bonding):把配对产生的密钥存下来,下次直接用。
二、配对三阶段
2.1 总时序
配对严格分三个阶段,任何一方偏离预期就是Pairing Failed。
2.2 Phase 1:配对特性交换
Phase 1 是三个阶段里唯一 “做决定” 的阶段,Phase 2、3 都是执行。因此,配对方式的决定因素仅存在于 Phase 1。
输入:双方 IO Capability + 双方 AuthReq + 双方 OOB flag ↓ 输出:本次配对方式(Just Works / Passkey / 数字比对 / OOB)2.2.1 报文结构
Pairing Request (0x01)与Pairing Response (0x02)结构完全相同,仅首字节不同,长度都是 7 字节:
┌──────┬───────────────┬──────────┬─────────┬──────────┬─────────┬─────────┐ │ Code │ IO Capability │ OOB flag │ AuthReq │ MaxKeySz │ InitKey │ RespKey │ │ 1B │ 1B │ 1B │ 1B │ 1B │ Dist 1B │ Dist 1B │ └──────┴───────────────┴──────────┴─────────┴──────────┴─────────┴─────────┘| 偏移 | 字段 | 说明 |
|---|---|---|
| 0 | Code | 0x01= Request,0x02= Response |
| 1 | IO Capability | 本机交互能力 |
| 2 | OOB data flag | 0x00= 无 OOB,0x01= 有 OOB |
| 3 | AuthReq | 安全需求位域(见下) |
| 4 | Maximum Encryption Key Size | 可接受的最大密钥长度,7 ~ 16 |
| 5 | Initiator Key Distribution | 发起方将分发哪些密钥 |
| 6 | Responder Key Distribution | 响应方将分发哪些密钥 |
2.2.2 AuthReq 位域(1 字节)
| bit | 名称 | 含义 |
|---|---|---|
| 0 | Bonding | 要求绑定(把密钥存下来) |
| 1 | RFU | 保留 |
| 2 | MITM | 要求中间人防护(= 要求认证) |
| 3 | SC | 支持 LE Secure Connections |
| 4 | Keypress | 支持按键通知(仅 LESC) |
| 5 | CT2 | 支持跨传输密钥派生(仅双模设备,纯 BLE 场景可忽略) |
速记常数值:
| AuthReq 值 | 拆解 | 含义 |
|---|---|---|
0x01 | Bonding | 只绑定,不要求认证 |
0x05 | Bonding + MITM | 绑定 + 要求认证 |
0x09 | Bonding + SC | 绑定 + 支持 LESC |
0x0D | Bonding + MITM + SC | 绑定 + 认证 + LESC |
0x2D | Bonding + MITM + SC + CT2 | 典型 Android 手机 |
2.2.3 Key Distribution 位域(1 字节)
| bit | 掩码 | 名称 | 分发内容 |
|---|---|---|---|
| 0 | 0x01 | EncKey | LTK + EDIV + Rand |
| 1 | 0x02 | IdKey | IRK + 身份地址 |
| 2 | 0x04 | Sign | CSRK |
| 3 | 0x08 | LinkKey | BR/EDR 链接密钥(双模) |
📌 这两个字段声明的是 “本机将分发哪些密钥”。实际分发范围,取双方声明与本机策略的交集。
2.3 Phase 2:认证与密钥生成
Phase 2 有两项任务:认证对端身份,并生成链路密钥。
2.3.1 Legacy 配对(第一代)
⚠️现已少见(现代设备默认采用 LESC)
这一阶段的全部内容 —— 只有两种报文:
| 报文 | 内容 | 作用 |
|---|---|---|
| Pairing Confirm | 16 字节校验值 | 承诺:先证明 “我知道某个秘密”,但不暴露它 |
| Pairing Random | 16 字节随机数(明文) | 揭示:把算校验值用的随机数公开,供对方验算 |
要解决的问题:在不泄露 TK(Temporary Key,临时密钥)的前提下,证明双方都知道 TK。
- 双方各自生成一个 16 字节随机数 r(本机私有,不发送)用 TK 和 r 算出一个校验值 → 作为 Confirm 报文发出(对方拿到 Confirm 也猜不出 TK,因为不知道 r)。
- 双方把随机数 r 明文发出 → 即 Random 报文。
- 双方用收到的 r 与自己的 TK 重算一次,比对结果是否等于对方先前发来的 Confirm,相等 → 证明对方确实知道 TK。
TK(临时密钥)的来源:
| 配对方式 | TK 的值 |
|---|---|
| Just Works | 16 字节全零 |
| Passkey Entry | 6 位十进制数字(0 ~ 999999) |
| OOB | 128 bit 随机数,经 BLE 之外的通道传递 |
从 TK 到链路密钥:STK = s1(TK, r1, r2)
⚠️ Legacy 的根本弱点:STK 完全由 TK 决定,而 TK 只有 20 bit 熵(10⁶ 种可能)。攻击者被动录下整段配对后,可离线穷举 10⁶ 次还原 TK —— 现代 CPU 只需毫秒级。
2.3.2 LE Secure Connections(LESC,第二代)
现代设备的默认路径。核心差别是用 ECDH 代替" 6 位数字当种子"。
本阶段共四种报文:
| 报文 | 内容 | 作用 |
|---|---|---|
Pairing Public Key(0x0C) | 65 字节(1 + 32 + 32) | 交换 ECDH 公钥 |
Pairing Confirm(0x03) | 16 字节校验值 | 同 Legacy 的承诺 |
Pairing Random(0x04) | 16 字节随机数 | 同 Legacy 的揭示 |
Pairing DHKey Check(0x0D) | 16 字节校验值 | 认证收口:证明双方算出的 DHKey 一致 |
LESC 改用 ECDH 生成密钥 —— 双方只交换公钥,各自用「本机私钥 + 对端公钥」能算出相同的结果(即 DHKey),而窃听者只有两个公钥,算不出(需解椭圆曲线离散对数,约 2¹²⁸ 次运算)。但 ECDH 本身挡不住中间人(攻击者可分别与双方各做一次 ECDH),所以还要再加一层 “认证收口”。
- 双方各自生成一对 ECDH P-256 密钥,把公钥明文发给对方(Public Key 报文)
- 双方各自用「本机私钥 + 对端公钥」算出同一个 DHKey
- 做与 Legacy 相同的"承诺 / 揭示"(Confirm / Random 报文),区别是校验值的计算里额外绑入了 DHKey,因此公钥若被中途替换就对不上
- 双方用 DHKey 派生出 LTK,再互发 DHKey Check 报文互相验证:校验通过 → 确认双方算出的 DHKey 一致,且中途无人替换过公钥
2.4 Phase 3:密钥分发
报文结构:
| Code | 名称 | 总长 | 结构 |
|---|---|---|---|
0x06 | Encryption Information | 17 | Code + LTK(16) |
0x07 | Central Identification | 11 | Code + EDIV(2) + Rand(8) |
0x08 | Identity Information | 17 | Code + IRK(16) |
0x09 | Identity Address Information | 8 | Code + AddrType(1) + BD_ADDR(6) |
0x0A | Signing Information | 17 | Code + CSRK(16) |
Phase 3 的报文有 5 种,但不是每种都会出现:
| 分发内容 | 对应报文 | Legacy | LESC | 用途 |
|---|---|---|---|---|
| LTK + EDIV + Rand | 0x06+0x07 | ✅ 分发 | ❌ 各自算出,不分发 | 加密链路 |
| IRK + 身份地址 | 0x08+0x09 | ✅ 分发 | ✅ 分发 | 以后解析对方的随机地址 |
| CSRK | 0x0A | ✅ 分发 | ✅ 分发 | 以后验证对方的签名 |
注意 IRK 与 CSRK 都不是用来加密的:它们是供以后使用的长期凭据(认出对方的随机地址 / 验证对方的签名),所以只有绑定时才有必要分发。
Central Identification (0x07)携带的 EDIV(2) + Rand(8) 是一组 “索引号”:主设备可能同时绑定了很多设备(手机、耳机、手环…)重连时从设备上报 (EDIV, Rand) → 主设备据此在密钥库里查出对应的是哪把 LTK。
三、配对方式、密钥与隐私
配对方式唯一的决定因素是双方的交互能力:
| 值 | 名称 | 能力 |
|---|---|---|
0x00 | DisplayOnly | 能显示 6 位数字,不能输入、不能按是/否 |
0x01 | DisplayYesNo | 能显示,能按 “是/否” |
0x02 | KeyboardOnly | 能输入数字,不能显示 |
0x03 | NoInputNoOutput | 无任何交互能力 |
0x04 | KeyboardDisplay | 既能显示又能输入(手机) |
3.1 四种配对方式
这四种方式均为抵御中间人攻击的手段,区别在于采用何种方式验证对端身份:
| 方式 | 带外验证手段 | 防窃听 | 防中间人 | 用户交互 | 典型场景 |
|---|---|---|---|---|---|
| Just Works | 无 | ✅ | ❌ | 无 | 无交互能力的传感器、追求快 |
| Passkey Entry | 6 位数字(单向输入) | ✅ | ✅ | 一端显示、一端输入 | 手环 / 键盘 |
| Numeric Comparison | 6 位数字(双向比对) | ✅ | ✅ | 双方确认 | 手机 ↔ 手机 |
| OOB | BLE 之外的通道 | ✅ | ✅ | 碰一碰 / 扫码 | NFC、二维码 |
四种方式中,仅 Just Works 无法抵御中间人攻击 ——因为它不进行任何验证。其余三种均依靠引入攻击者无法伪造的信息来抵御中间人:用户已知的密码 / 用户可见的数字 / BLE 之外的通道。
3.2 决策流程
- 双方都支持 LESC 且任一方有 OOB 数据? → 是:直接用 OOB(最强)
- 对方 IO 能力值非法(> 0x04)? → 是:降级 Just Works
- 双方都不要求 MITM(AuthReq 位都是 0)? → 是:直接 Just Works ★
- 以上都不是 → 查下面的决策表
“3” 是最容易被忽略的一条:即使双方都有屏幕,只要 AuthReq 的 MITM 位双方都没置,结果仍然是 Just Works。有屏幕不等于会用屏幕做验证。
LESC 决策表:
行 = 对方 IO 能力,列 = 本机 IO 能力:
| 对方 \ 本机 | 0 DisplayOnly | 1 DisplayYesNo | 2 KeyboardOnly | 3 NoIO | 4 KeyboardDisplay |
|---|---|---|---|---|---|
| 0 DisplayOnly | Just Works | Just Works | Passkey 输入 | Just Works | Passkey 输入 |
| 1 DisplayYesNo | Just Works | 数字比对 | Passkey 输入 | Just Works | 数字比对 |
| 2 KeyboardOnly | Passkey 显示 | Passkey 显示 | Passkey 输入 | Just Works | Passkey 显示 |
| 3 NoIO | Just Works | Just Works | Just Works | Just Works | Just Works |
| 4 KeyboardDisplay | Passkey 显示 | 数字比对 | Passkey 输入 | Just Works | 数字比对 |
3.3 Just Works 无法抵御中间人攻击的原因
- A 发起配对,M 拦截后转给 B,但把 A 的公钥换成自己的 P_M1(冒充 A)
- B 回复,M 拦截后转给 A,但把 B 的公钥换成自己的 P_M2(冒充 B)
- A 算出 DHKey_A = d_A × P_M2(A 以为 P_M2 是 B 的公钥),B 算出 DHKey_B = d_B × P_M1(B 以为 P_M1 是 A 的公钥),两个值 M 都知道 —— 因为 P_M1、P_M2 的私钥都在 M 手上
- Just Works 的 Confirm 计算里没有任何 “用户提供的秘密” → M 可以正常完成两边的 Confirm / Random / DHKey Check
- A、B 都以为配对成功,但 M 持有两把独立的密钥
一句话总结:Just Works = 加密 ✅ + 认证 ❌
Passkey / 数字比对 / OOB 之所以能挡住中间人, 本质是引入了攻击者无法伪造的信息:用户知道的密码 / 用户看到的数字 / BLE 之外的通道。
3.4 秘钥体系
| 密钥 | 全称 | 作用 | 参与加密? |
|---|---|---|---|
| LTK | Long Term Key | 加密这条链路 | ✅ 是(唯一可长期复用的) |
| IRK | Identity Resolving Key | 认出对方的随机地址 | ❌ 否 |
| CSRK | Connection Signature Resolving Key | 验证数据签名 | ❌ 否 |
LTK 决定链路能否被窃听,IRK 决定能否识别对端身份,CSRK 决定数据签名是否可信。
绑定 = 把配对产生的密钥持久化到 flash,下次连接无须重新配对。存储内容:
- 对端身份地址,对方的固定身份地址(作为查找索引)
- LTK,16 字节密钥 + EDIV(2) + Rand(8)
- IRK,16 字节
- CSRK,16 字节 + 签名计数器(防重放)
- 标志位,是否 LESC、是否已认证、加密密钥长度
重连:跳过配对直接加密
- 从设备发起加密请求,携带 (EDIV, Rand)
- 主设备用 (EDIV, Rand) 在密钥库里查出对应的 LTK
- 双方用这把 LTK 建立加密 → 全程不需要重新配对
(EDIV, Rand)相当于 LTK 的 “索引号”:一个主设备可能绑定了多台设备,重连时靠这组索引找出该用哪把 LTK。
绑定的生命周期:
| 事件 | 结果 |
|---|---|
| 配对 + 绑定 | 密钥写入 flash,长期有效 |
| 断开连接 | 绑定仍在 |
| 任一方"忘记设备" | 该侧删除绑定 → 下次重连退回完整配对流程(另一侧可能仍保留旧绑定) |
| 擦除 flash / 恢复出厂 | 绑定全部丢失 → 所有设备都要重新配对 |
| 密钥库满 | 按策略拒绝新绑定,或覆盖最久未用的(取决于实现) |
3.5 隐私与随机地址
引入问题:固定地址 = 永久标识符。BLE 地址是 48 bit。如果设备始终用同一个地址:
商场 A 的嗅探器:10:00 有个设备 7C:E8:... 商场 B 的嗅探器:10:30 有个设备 7C:E8:... 商场 C 的嗅探器:11:00 有个设备 7C:E8:... ↓ 推断:同一个人,逛了 A→B→C地址类型:
| 类型 | 说明 | 能否变化 |
|---|---|---|
| Public Address | IEEE 分配的公共地址,全球唯一 | ❌ 固定 |
| Random Static | 随机静态地址,设备自己生成 | ❌ 每次上电可换,但运行期固定 |
| RPA(可解析私有地址) | 随机变化,但持有 IRK 者能认出 | ✅ 周期性变化 |
| NRPA(不可解析) | 纯随机,谁都认不出 | ✅ 变化 |
随机地址的子类型靠最高 2 bit 区分:
| 子类型 | 最高 2 bit | 打印地址的首字节范围 |
|---|---|---|
| NRPA | 00 | 0x00~0x3F |
| RPA | 01 | 0x40~0x7F |
| Random Static | 11 | 0xC0~0xFF |
最高 2 bit 的规则只对随机地址成立。公共地址的最高 2 bit 没有任何含义,可以是任意值。所以必须先看地址类型(Public / Random),再看首字节。
四、安全等级与 ATT 权限
Security Mode 1 的四个等级:
| 等级 | 名称 | 条件 | 可抵御的威胁 |
|---|---|---|---|
| Level 1 | No security | 不加密、不认证 | 均不能抵御 |
| Level 2 | Unauthenticated pairing with encryption | 加密,但不认证 | 挡窃听(①) |
| Level 3 | Authenticated pairing with encryption | 加密 + 认证(MITM) | ① + ② |
| Level 4 | Authenticated LE Secure Connections pairing with encryption | LESC + 加密 + 认证 + 128 bit 密钥 | ① + ②(最强) |
如何指定安全等级:连接建立后主动提出安全需求。抓包里会看到一个Security Request (0x0B)报文。
要求高等级会自动拒绝弱配对:如果要求 Level 3 或 Level 4,但 Phase 1 算出的配对方式是 Just Works,协议栈自动发出Pairing Failed (0x05)+ 原因码0x03 (Auth Requirements)
GATT 属性上可以设置四种读权限(写权限对称):
| 权限 | 含义 | 对应安全等级 |
|---|---|---|
| Read | 明文可读 | Level 1 |
| Read Encrypt | 必须加密链路 | Level 2 |
| Read Authenticate | 必须加密 + 认证 | Level 3 |
| Read LESC | 必须是 LESC 产生的密钥 | Level 4 |
权限不出现在空口上 —— 它是服务端的内部策略,客户端只能通过 “读失败” 的错误码间接发现它。
规范原文:如果既没有 LTK 也没有 STK → 回
0x05 Insufficient Authentication;如果有 LTK/STK、要求加密但加密未启用 → 回0x0F Insufficient Encryption。