4. BLE 配对与绑定机制
好,咱们今天来聊聊 BLE 配对和绑定。这玩意儿,说白了就是让两个蓝牙设备互相认识、建立信任的过程。我刚开始做蓝牙项目时,总觉得配对就是点一下“连接”就完事了,结果产品在客户手里死活连不上,后来才发现是配对流程没走对。嗯,这里面的坑,咱们一个一个填。
4.1 配对流程:三步走
BLE 配对不是一锤子买卖,它分三个阶段。我习惯把它叫做“握手、验证、分钥匙”。
- Pairing Feature Exchange(配对特性交换):双方互相亮底牌——你支持什么 IO 能力?你用什么加密算法?说白了就是先看看对方能干啥。
- Pairing Authentication(配对认证):根据第一步的结果,选择具体的配对方式。这一步可能会弹窗让你输 PIN 码,或者让你确认数字。
- Transport Specific Key Distribution(传输特定密钥分发):认证通过后,双方交换加密密钥、身份解析密钥等。这些密钥存下来,下次连接就不用再配对了。
核心要点:配对是临时过程,绑定是持久化存储密钥。配对成功不一定绑定,但绑定一定先配对。
4.2 四种配对方式:选哪个?
配对方式的选择,取决于设备的 IO 能力。我做过一个智能门锁项目,客户要求“无屏无键盘”,那就只能用 Just Works。你想想看,一个锁上连个按钮都没有,怎么输 PIN 码?
| 配对方式 | IO 能力要求 | 安全等级 | 典型场景 |
|---|---|---|---|
| Just Works | 无要求 | 低(无 MITM 保护) | 传感器、灯泡 |
| Passkey Entry | 一方可输入,一方可显示 | 中 | 键盘、门锁 |
| Numeric Comparison | 双方都可显示和确认 | 高 | 手机配对、遥控器 |
| OOB(Out of Band) | 额外通信通道(NFC、二维码) | 最高 | 支付设备、医疗设备 |
避坑指南:我曾经在一个项目中用了 Just Works,结果客户反馈“别人拿手机也能连我的灯”。后来加了 Passkey Entry,虽然用户体验差了点,但安全多了。记住:Just Works 不防中间人攻击。
4.3 绑定与密钥存储
绑定就是把配对过程中生成的密钥存到非易失性存储器里。下次两个设备再见面,直接加载密钥,跳过配对流程。
密钥存哪里?我一般建议存到 Flash 的专用分区里。注意几个要点:
- LTK(Long Term Key):长期密钥,加密通信用的。
- IRK(Identity Resolving Key):身份解析密钥,用来识别对方设备。
- CSRK(Connection Signature Resolving Key):连接签名解析密钥,用于数据签名验证。
嗯,这里要注意:绑定信息是跟设备地址绑定的。如果设备地址变了(比如随机地址轮换),绑定就失效了。我踩过这个坑——产品出货后,用户手机重启就搜不到设备了,查了半天发现是地址类型没配对。
4.4 实战:实现 BLE 配对与绑定
咱们用 Nordic nRF5 SDK 举个例子。这段代码我调过很多次,基本是标准流程。
// 初始化配对参数
ble_gap_sec_params_t sec_params;
memset(&sec_params, 0, sizeof(sec_params));
sec_params.bond = 1; // 使能绑定
sec_params.mitm = 1; // 使能中间人保护
sec_params.lesc = 1; // 使用 LE Secure Connections
sec_params.keypress = 0; // 不使能按键通知
sec_params.io_caps = BLE_GAP_IO_CAPS_DISPLAY_ONLY; // 仅显示
sec_params.oob = 0; // 不使用 OOB
sec_params.min_key_size = 7; // 最小密钥长度
sec_params.max_key_size = 16; // 最大密钥长度
// 发起安全请求
uint32_t err_code = sd_ble_gap_sec_params_reply(m_conn_handle,
BLE_GAP_SEC_STATUS_SUCCESS,
&sec_params,
NULL);
APP_ERROR_CHECK(err_code);
个人经验:我习惯把 io_caps 参数做成可配置的宏定义。这样同一个固件,通过修改宏就能切换配对方式,调试时特别方便。
绑定信息的存储,SDK 通常有现成的模块。比如 nRF5 的 peer_manager,它会自动管理绑定数据的读写。你只需要在初始化时调用:
// 初始化 Peer Manager
ret_code_t err_code = pm_init();
APP_ERROR_CHECK(err_code);
// 注册安全事件回调
err_code = pm_sec_manager_register(&sec_manager_cb);
APP_ERROR_CHECK(err_code);
回调函数里处理配对状态:
static void sec_manager_cb(pm_evt_t const * p_evt)
{
switch (p_evt->evt_id)
{
case PM_EVT_CONN_SEC_SUCCEEDED:
// 配对/绑定成功
NRF_LOG_INFO("Pairing/Bonding succeeded");
break;
case PM_EVT_CONN_SEC_FAILED:
// 配对/绑定失败
NRF_LOG_ERROR("Pairing/Bonding failed, reason: %d",
p_evt->params.conn_sec_failed.error);
break;
case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED:
// 绑定数据更新成功
NRF_LOG_INFO("Bonding data updated");
break;
default:
break;
}
}
4.5 配对流程全景图
下面这张图,是我自己画的标准 BLE 配对流程。你看一眼,基本就能把整个流程串起来。
4.6 常见问题与调试技巧
调试配对问题,我一般用 nRF Connect 或者 LightBlue 抓日志。几个常见坑:
- 配对请求无响应:检查从机是否调用了
sd_ble_gap_sec_params_reply。我遇到过从机没调用这个函数,主机一直等,超时断开。 - 绑定信息丢失:检查 Flash 存储空间是否够用。绑定数据一般几百字节,但设备多了会占满。
- 配对失败但没回调:检查安全回调函数是否注册了。嗯,这个我犯过两次,都是低级错误。
调试口诀:先看 IO 能力是否匹配,再看密钥长度是否一致,最后查 Flash 存储是否正常。
好了,BLE 配对与绑定这块,核心就是三步流程加四种方式。你只要把 IO 能力配对了,密钥存对了,基本不会出大问题。下次咱们聊 Wi-Fi AT 指令的配置,那个坑更多,哈哈。