BLE 配对加密
BLE 设备地址
BLE 设备之间连接,靠的是地址——相当于门牌号。
| 地址类型 | 比喻 | 特点 | 谁在用 |
|---|---|---|---|
| PUBLIC 地址(公共地址) | 固定门牌号 | 芯片出厂写入,全球唯一,永不变 | master 镜像的 TV |
| RANDOM 静态地址 | 自己贴的门牌号 | 每次开机可能变,但同一生命周期内不变 | 遥控器常见 |
| RPA(Resolvable Private Address,可解析私有地址) | 加密门牌号 | 每次开机(甚至每次连接)都变;只有持有 IRK 的人才能把它"解开"看到真实身份 | 6.1 镜像的 TV(配置启用时) |
关键概念:IRK(Identity Resolving Key,身份解析密钥)。它是一把"万能解码器"——如果设备 A 在配对时把自己的 IRK 给了设备 B,那么之后 A 无论换了多少个 RPA 门牌号,B 都能用 IRK 解出"哦,这是 A"。
核心要点:用了 RPA,就必须把 IRK 分发给对端。否则对端看到的只是一个个不认识的随机地址。
IRK(身份解析密钥)是 BLE 隐私特性(Privacy)中的核心 16 字节密钥,用于:
-
生成 RPA:用 IRK 对随机数做 AES-128 加密,生成"可解析的私有地址"
-
解析 RPA:持有同一 IRK 的对端能把这个随时变化的随机地址还原成设备的真实身份地址(Identity Address),实现"防跟踪但认得出来"
代码中定义:
#define SMP_IRK_LEN 0x10 // 固定 16 字节
IRK 在 BLE 安全体系中的位置
BLE 配对/绑定后产生一组"分发密钥"(Distribution Keys),(对端密钥块)
uint8_t IRK[SMP_IRK_LEN]; // 对端 IRK
uint8_t LTK[SMP_ENC_KEY_LEN]; // 长期加密密钥(链路加密用)
uint8_t CSRK[SMP_CSRK_LEN]; // 签名密钥(数据签名用)
uint16_t EDIV; uint64_t Rand; // 加密参数
-
LTK → 加密数据链路
-
CSRK → 数据签名(防止篡改)
-
IRK → 地址隐私(身份识别),三者职责分离
配对成功后结果上抛,包含本端与对端 IRK (void)memcpy_s(pairResult.localIRK, SMP_IRK_LEN, g_smpPairMng.local.IRK, SMP_IRK_LEN); (void)memcpy_s(pairResult.peerIRK, SMP_IRK_LEN, g_smpPairMng.peer.IRK, SMP_IRK_LEN);
IRK 的生命周期
-
本机 IRK 的注入 上层应用/系统配置通过 BTM → SMP 逐级设置:
-
服务层:ble_adapter.cpp→ BTM_SetLocalIdentityResolvingKey(&btmKey)
-
安全层:btm_le_sec.c→ 存入 g_localIdentityResolvingKey(每台设备一份)
-
SMP 层: SMP_SetIRK() → SMP_SetLocalIrk() 写入 g_smpLocalIRK 同时还需设置本机身份地址 SMP_SetIdentAddr( 默认值示例): static BtAddr g_smpLocalIdentAddr = { .type = 0x01, .addr = {0xFF, 0x01, 0x02, 0x03, 0x04, 0x05} };
-
配对时的 IRK 分发(获取对端 IRK) 配对成功后进入密钥分发阶段,IRK 通过 SMP 命令 Identity Information 传输(在已加密链路上):
-
发送方:smp_cmd.c:260 SMP_SendIdentityInformation(),明文载荷 = 16 字节 IRK: PacketPayloadWrite(pkt, irk, offset, SMP_IRK_LEN); //
-
接收方:主设备/ 从设备,校验配对状态机步骤后存入对端密钥块: if (memcpy_s(SMP_GetPairMng()->peer.IRK, SMP_IRK_LEN, (uint8_t *)BufferPtr(buffer), SMP_IRK_LEN) != EOK) ... 之后还有 Identity Address Information 命令将 IRK 与真实身份地址绑定下发。
-
Secure Connections 模式:IRK 可从 DHKey 派生 SC 配对时双方交换 P-256 公钥,用 HCI_LeGenerateDHKey 计算 DHKey(,DHKey 经 key derivation(k1/k2/k3 函数)派生 LTK/IRK/CSRK。
-
持久化存储 每台已配对设备的 IRK 存档在安全数据库中,供日后解析:
-
BTM_GetRemoteIdentityResolvingKey(addr, irk):按地址查某台设备的 IRK
-
BTM_GetAllRemoteIdentityResolvingKey(&irks, &count):导出所有已配对设备的 IRK 列表
-
扫码时配对信息从设备数据库恢复:把历史 IRK 写回 pairedDevice.remoteIdentityResolvingKey
IRK 的核心用途:生成与解析 RPA
生成 RPA( SMP_GenerateRPA → SMP_GenRpaTask)
uint32_t number = RandomGenerate(); // 随机数
memcpy_s(address, sizeof(address), &number, SMP_RPA_HIGH_BIT_LEN); // 低24位 = prand
address[0x00] &= 0x7Fu; address[0x00] |= 0x40u; // 最高2位置 0b01
encryptParam.key = param->irk; // 密钥 = IRK
SMP_SendLeEncryptCmd(&encryptParam, SMP_GENERATE_RPA_STEP_1, ...); // AES-128
回调中补齐高 24 位哈希:address + SMP_RPA_HIGH_BIT_LEN = AES 加密结果 → 完成 RPA = hash(IRK, prand) | prand。
解析 RPA( SMP_ResolveRPA)
memcpy_s(message, ..., addr + SMP_RPA_HIGH_BIT_LEN, SMP_RPA_HIGH_BIT_LEN); // 取出prand
ret = SMP_Aes128(irk, SMP_IRK_LEN, message, sizeof(message), encryptedData);
if (memcmp(encryptedData, addr, SMP_RPA_HIGH_BIT_LEN) != 0x00) {
ret = SMP_RESOLVE_RPA_RESULT_NO; // 哈希不符 → 不是该IRK生成的地址
} else {
ret = SMP_RESOLVE_RPA_RESULT_YES; // 匹配 → 身份确认
}
异步版 SMP_AsyncResolveRPA投递到 SMP 处理队列,SMP_ResolveRpaStep1用 AES 结果逐位 memcmp 比对。
扫描侧批量解析
收到广播报告后,对地址类型为随机的设备用已配对设备的 IRK 列表逐一尝试:
const uint8_t *keyPtr = info->IRKList[info->resolveIndex].irk.key;
ret = SMP_AsyncResolveRPA(addrPtr, keyPtr);
解析成功后在 GapResolveRPAResult里把随机地址替换回真实身份地址并上抛上层:
BTM_UpdateCurrentRemoteAddress(&info->IRKList[info->resolveIndex].addr, &info->addr);
// 此后上层看到的都是稳定的身份地址,而非15分钟一换的随机地址
安全特征
| 特性 | 代码体现 |
|---|---|
| IRK 永不裸传 | 仅在配对完成后经加密链路用 Identity Information 命令分发 |
| 防跟踪 | 外人无 IRK,只能看到每 15 分钟轮换的随机地址 |
| 可识别 | 持 IRK 者可还原身份地址,跨设备互认 |
| 硬件加速可选 | hardware/software AES-128 双实现, SMP_Aes128Hardware/Software |
| IRK 丢失即身份失效 | 被删绑定后 IRK 清除,对方将无法再解析本设备 RPA |
全景链路
上层应用/BleConfig ──设置──▶ BTM_SetLocalIdentityResolvingKey │ ▼ SMP_SetIRK──▶ g_smpLocalIRK ├──▶ 生成RPA: SMP_GenerateRPA ── AES-128(IRK,prand) ──▶ 广播每15分钟换新地址 │ ├──▶ 配对分发: SMP_SendIdentityInformation ──加密链路──▶ 对端 peer.IRK │ ├──▶ 扫描解析: 广播地址 + IRKList ──▶ SMP_AsyncResolveRPA ──▶ 还原真实身份地址 │ └──▶ 设备数据库存储──▶ dsoftbus 组网识别
一句话总结:IRK 是 BLE 隐私机制的灵魂密钥——持有 IRK 就能生成"看起来随机、可自行解析"的私有地址,让陌生设备无法持续跟踪你,而信任设备(已配对)永远认得你。本仓库中从 SMP 协议到 GAP 扫描、安全层存储再到跨设备组网(dsoftbus)完整实现了这套机制。
RPA(Resolvable Private Address,可解析私有地址)是 BLE 的 Privacy 特性。
地址类型枚举见 foundation/communication/bluetooth/interfaces/inner_api/include/bluetooth_def.h
-
PUBLIC=0x00 公开地址
-
RANDOM=0x01 随机地址
-
NON_RPA=0x02 不可解析私有地址
-
RPA=0x03 可解析私有地址
RPA 是 BLE 隐私机制(Privacy 1.2)中用于防跟踪的地址:设备以每 15 分钟轮换的随机地址对外广播/连接,外人无法关联到同一设备;而已配对设备因持有双方的 IRK(身份解析密钥),能把这个随机地址"解析"回设备真实身份地址(Identity Address)。
地址格式(48 位) ┌────────────── 48 bit 蓝牙地址 ──────────────┐ │ 最高2位 = 0b01 (随机私有类型) │ │ ┌──────────────┬──────────────┐ │ │ │ hash(24bit) │ prand(24bit)│ │ │ └──────────────┴──────────────┘ │ │ hash = AES-128(IRK, prand) 取低24位 │ └─────────────────────────────────────────────┘
-
生成方: SMP_GenerateRPA() 用本地 IRK(Identity Resolving Key,16 字节)对 24 位随机数 prand 做 AES-128 加密,取结果低 24 位作哈希拼出地址
-
解析方: SMP_AsyncResolveRPA() 对端设备持有同一 IRK,对接收到的 RPA 重新计算,哈希比对一致即可将随机地址还原为真实身份地址(Identity Address),从而实现"外人看不出是谁,熟人认得出"
#define SMP_RPA_HIGH_BIT_LEN 0x03 /* hash与prand各占3字节(24bit) */
constexpr int BLE_CHANGE_RPA_ADDRESS_INTERVAL = 15 * 60 * 1000; /* 15分钟轮换 */
RPA 生成完整链路
-
服务层触发 广播开启且地址类型为 RPA 时请求"生成可解析私有地址":
SetRpaAddrAndTypeToBtm()
case BLE_ADDR_TYPE_RPA: {
ret = GAPIF_LeGenResPriAddr(&BleAdapter::GenResPriAddrResult, this);
// 阻塞等待回调(BLE_THREAD_WAIT_TIMEOUT 超时保护)
}
-
GAP 接口封装 GAPIF_LeGenResPriAddr() // → GapRunTaskBlockProcess → GAP_LeGenResPriAddrAsync → SMP_GenerateRPA
-
SMP 核心:随机数与地址组装( SMP_GenerateRPA → SMP_GenRpaTask)
uint32_t number = RandomGenerate(); // 取随机数
memcpy_s(address, ..., &number, SMP_RPA_HIGH_BIT_LEN); // 低24位作为prand
address[0x00] &= 0x7Fu; // 清最高位
address[0x00] |= 0x40u; // 置最高2位=0b01
...
encryptParam.key = param->irk; // AES-128 密钥 = IRK
SMP_SendLeEncryptCmd(&encryptParam, SMP_GENERATE_RPA_STEP_1, ...);
-
加密完成回调:补全 hash( SMP_GenerateRpaStep1)
SMP_MemoryReverseCopy(pEncCmdData->address + SMP_RPA_HIGH_BIT_LEN, //
returnParam->encryptedData, SMP_RPA_HIGH_BIT_LEN); // 加密结果低24位 → 高24位
SMP_MemoryReverseCopy(address, pEncCmdData->address, sizeof(address));
SMP_NotifyCbGenRpa(SMP_GENERATE_RPA_STATUS_SUCCESS, address);
-
写回控制器(蓝牙地址生效) 生成回调 BleAdapter::GenResPriAddrResult将 RPA 通过 GAP/BTM 下发给 HCI:
btAddr.type = BLE_ADDR_TYPE_RANDOM;
BTM_SetLeRandomAddress(&btAddr); // → HCI_LeSetRandomAddress → 控制器
BTM_SetOwnAddressType(BLE_ADDR_TYPE_RANDOM);
-
扩展广播(EXT ADV)单独设地址 //GenResPriAddrResultTask() GAPIF_LeExAdvSetRandAddr(advStartHandle, &btAddr.addr[0]); // 传统广播: GAPIF_LeAdvSetRandAddr
15 分钟轮换机制(隐私防跟踪的精髓)
广播启动成功即挂一个周期性定时器:
if (BleConfig::GetInstance().GetBleAddrType() == BLE_ADDR_TYPE_RPA) {
startIter->second.timer_ =
std::make_unique<utility::Timer>(std::bind(&TimerCallback, this, advStartHandle));
startIter->second.timer_->Start(BLE_CHANGE_RPA_ADDRESS_INTERVAL, true); // 15分钟,重复
}
到点执行 TimerCallback——先停播、换地址、再开播,避免旧地址残留:
SetAdvEnableToGap(false); // ① 停止广播
GAPIF_LeGenResPriAddr(&GenResPriAddrResult, advertiser); // ② 生成新RPA(阻塞等待)
SetAdvEnableToGap(true); // ③ 用新地址重新广播
扩展广播对应 TimerCallbackEx,轮换完在 GenResPriAddrResultTask 里重新开启。 地址轮换期(合法扫描窗口内广播停止)由 STOP_ADV_TYPE_RESOLVING_LIST 流程管理:停播失败则立即重试。
RPA 解析完整链路(扫描/连接侧认"老朋友")
-
扫描收到随机地址后批量尝试解析维护 reportRPAResolveList,对每条广播报告,用所有已配对设备的 IRK 轮流试:
const uint8_t *keyPtr = info->IRKList[info->resolveIndex].irk.key;
ret = SMP_AsyncResolveRPA(addrPtr, keyPtr); // 投递到SMP队列异步解析
IRK 列表来源:BTM_GetAllRemoteIdentityResolvingKey(),即从已配对设备数据库导出所有 IRK。
-
SMP 解析实现 SMP_AsyncResolveRPA→ SMP_ResolveRpaStep1:
// 用待试IRK对地址hash部分做AES-128,与prand比较
if (memcmp(returnParam->encryptedData, pEncCmdData->address,
BT_ADDRESS_SIZE - SMP_RPA_HIGH_BIT_LEN) != 0x00) {
SMP_ResoRpaStep1Failed(pEncCmdData); // 不匹配 → 换下一个IRK
} else {
SMP_ResoRpaStep1Success(pEncCmdData); // 匹配 → 身份确认
}
同步版 SMP_ResolveRPA内部同样是 SMP_Aes128(irk, ...) 后 memcmp。
-
解析成功后替换为真实身份地址
GapResolveRPAResult:
if (status == SMP_PAIR_STATUS_SUCCESS && result == true) {
if (memcmp(...irk...) == 0) {
BTM_UpdateCurrentRemoteAddress(&info->IRKList[info->resolveIndex].addr, &info->addr);
// 把扫描到的随机地址替换成该配对设备的身份地址后上抛
}
info->doCallback = true; // 触发上层地址解析事件
} else {
info->resolveIndex++; // 换下一个IRK继续试
}
此后上层看到的是稳定身份地址,可与设备数据库关联,实现"换地址也认得出"。 全链路图
【生成侧】
BleConfig(addrType=RPA) ─▶ BleAdapter::SetRpaAddrAndTypeToBtm
└─▶ GAPIF_LeGenResPriAddr
└─▶ SMP_GenerateRPA (smp.c:878) ──RandomGenerate──▶ prand
└─▶ AES-128(IRK, prand)
└─▶ SMP_GenerateRpaStep1 拼装 2bit|hash|prand
└─▶ BTM_SetLeRandomAddress ─▶ HCI_LeSetRandomAddress ─▶ 控制器
└─▶ 每15分钟定时器: 停播→重生成→开播
【解析侧】
收到广播/扫描报告(addrType=RANDOM)
└─▶ IRK列表逐个试: SMP_AsyncResolveRPA
└─▶ SMP_ResolveRpaStep1 memcmp比较
✓ 匹配 ─▶ GapResolveRPAResult ─▶ BTM_UpdateCurrentRemoteAddress 还原身份地址
✗ 不匹配 ─▶ resolveIndex++ 试下一个IRK
RPA = 用 IRK 周期生成的"可自解析随机地址",本仓库实现要点:
-
防跟踪:15 分钟一轮换(BLE_CHANGE_RPA_ADDRESS_INTERVAL),外人看到的地址永远在变
-
可识别:配对设备持 IRK 可逆推出身份(SMP_ResolveRPA + IRK 列表遍历)
-
三层协作:service 层(定时器与地址类型配置)→ GAP 层(扫描解析调度、地址下发)→ SMP 层(AES 加密生成/解析核心)
-
密钥前提:RPA 的生死完全由 IRK 决定(上一条回复),两者是 BLE 隐私机制的一体两面
SMP 配对与密钥分发:交换钥匙的过程
BLE 配对(SMP 协议)的本质是双方协商并交换"钥匙",配对成功后双方各自保存一份bond(信任档案),里面记录着对方的地址和钥匙:
| 密钥 | 作用 | 由哪条 SMP 消息分发 |
|---|---|---|
| LTK(长期密钥) | 加密连接的"大门钥匙" | Encryption Information(加密信息) |
| EDIV / Rand | LTK 的"编号",找回钥匙用 | Central Identification |
| IRK | 解析对方 RPA 地址的"解码器" | Identity Information + Identity Address(身份信息) |
回连(Reconnection)流程
配对完成后,以后每次开机都不用重新配对,而是走"回连":
遥控器按键唤醒 → 发广播(含地址)→ TV 扫描到 → 发起连接 → 加密验证(TV 用 bond 里的 LTK 加密链路)→ 加密成功 → HID 通道建立 → 可用
关键点:加密验证的前提是"双方都能把对方的地址和 bond 对上号"。TV 对遥控器加密,遥控器必须能回答"我认识你吗、你的钥匙是哪个"。
更多推荐

所有评论(0)