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 字节密钥,用于:

  1. 生成 RPA:用 IRK 对随机数做 AES-128 加密,生成"可解析的私有地址"

  2. 解析 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 的生命周期

  1. 本机 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} };

  1. 配对时的 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 与真实身份地址绑定下发。

  1. Secure Connections 模式:IRK 可从 DHKey 派生 SC 配对时双方交换 P-256 公钥,用 HCI_LeGenerateDHKey 计算 DHKey(,DHKey 经 key derivation(k1/k2/k3 函数)派生 LTK/IRK/CSRK。

  2. 持久化存储 每台已配对设备的 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 生成完整链路

  1. 服务层触发 广播开启且地址类型为 RPA 时请求"生成可解析私有地址":

SetRpaAddrAndTypeToBtm()
case BLE_ADDR_TYPE_RPA: {
 ret = GAPIF_LeGenResPriAddr(&BleAdapter::GenResPriAddrResult, this);
 // 阻塞等待回调(BLE_THREAD_WAIT_TIMEOUT 超时保护)
}
  1. GAP 接口封装 GAPIF_LeGenResPriAddr() // → GapRunTaskBlockProcess → GAP_LeGenResPriAddrAsync → SMP_GenerateRPA

  2. 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, ...);
  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);
  1. 写回控制器(蓝牙地址生效) 生成回调 BleAdapter::GenResPriAddrResult将 RPA 通过 GAP/BTM 下发给 HCI:

btAddr.type = BLE_ADDR_TYPE_RANDOM;
BTM_SetLeRandomAddress(&btAddr);      // → HCI_LeSetRandomAddress → 控制器
BTM_SetOwnAddressType(BLE_ADDR_TYPE_RANDOM);
  1. 扩展广播(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 解析完整链路(扫描/连接侧认"老朋友")

  1. 扫描收到随机地址后批量尝试解析维护 reportRPAResolveList,对每条广播报告,用所有已配对设备的 IRK 轮流试:

const uint8_t *keyPtr = info->IRKList[info->resolveIndex].irk.key;
ret = SMP_AsyncResolveRPA(addrPtr, keyPtr);   // 投递到SMP队列异步解析

IRK 列表来源:BTM_GetAllRemoteIdentityResolvingKey(),即从已配对设备数据库导出所有 IRK。

  1. 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。

  1. 解析成功后替换为真实身份地址

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 周期生成的"可自解析随机地址",本仓库实现要点:

  1. 防跟踪:15 分钟一轮换(BLE_CHANGE_RPA_ADDRESS_INTERVAL),外人看到的地址永远在变

  2. 可识别:配对设备持 IRK 可逆推出身份(SMP_ResolveRPA + IRK 列表遍历)

  3. 三层协作:service 层(定时器与地址类型配置)→ GAP 层(扫描解析调度、地址下发)→ SMP 层(AES 加密生成/解析核心)

  4. 密钥前提:RPA 的生死完全由 IRK 决定(上一条回复),两者是 BLE 隐私机制的一体两面

SMP 配对与密钥分发:交换钥匙的过程

BLE 配对(SMP 协议)的本质是双方协商并交换"钥匙",配对成功后双方各自保存一份bond(信任档案),里面记录着对方的地址和钥匙:

密钥作用由哪条 SMP 消息分发
LTK(长期密钥)加密连接的"大门钥匙"Encryption Information(加密信息)
EDIV / RandLTK 的"编号",找回钥匙用Central Identification
IRK解析对方 RPA 地址的"解码器"Identity Information + Identity Address(身份信息)

回连(Reconnection)流程

配对完成后,以后每次开机都不用重新配对,而是走"回连":

遥控器按键唤醒 → 发广播(含地址)→ TV 扫描到 → 发起连接
→ 加密验证(TV 用 bond 里的 LTK 加密链路)→ 加密成功 → HID 通道建立 → 可用

关键点:加密验证的前提是"双方都能把对方的地址和 bond 对上号"。TV 对遥控器加密,遥控器必须能回答"我认识你吗、你的钥匙是哪个"。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐