8. 蓝牙配对与绑定:经典蓝牙配对流程(PIN/Passkey)、BLE配对(Just Works/Numeric Comparison)、绑定键的存储与删除
蓝牙配对,说白了就是两台设备互相确认身份的过程。而绑定,则是把配对后生成的密钥存下来,下次不用再配一遍。
很多开发者容易把这两个概念搞混。我刚开始做蓝牙开发时,也踩过这个坑——以为配对成功就等于绑定成功。其实不是。配对是握手,绑定是记住握手的结果。你想想看,如果每次连接都要重新配对,那用户体验得多糟糕。
8.1 经典蓝牙配对:PIN与Passkey
经典蓝牙(BR/EDR)的配对,主要分两种方式:PIN码配对和Passkey配对。嗯,这里要注意,虽然名字有点像,但底层机制完全不同。
8.1.1 PIN码配对
PIN码配对是早期蓝牙设备常用的方式。用户需要输入一个固定的PIN码(通常是4位数字),两端设备用这个PIN码生成初始密钥。
流程大致是这样的:
- 发起端发送配对请求
- 两端设备提示用户输入PIN码
- 用PIN码生成链路密钥(Link Key)
- 完成认证,建立加密连接
我在项目中遇到过一个问题:有些老式蓝牙耳机,PIN码是写死在固件里的(比如0000)。但Android系统默认会弹窗让用户输入,结果用户不知道输什么,就卡住了。后来我建议在代码里预判设备类型,如果是已知的固定PIN设备,直接自动填充。
核心要点:PIN码配对的安全性取决于PIN码的复杂度。0000这种码,说白了就是形同虚设。
8.1.2 Passkey配对
Passkey配对是PIN码的升级版。它会在设备屏幕上显示一个6位数字,用户确认两端显示的数字一致即可。
我个人习惯用Passkey配对,原因很简单:用户体验好。用户不需要记住任何密码,只需要看一眼屏幕,点一下确认。
Android中触发Passkey配对的代码示例:
// 创建蓝牙设备对象
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
// 发起配对
device.createBond();
// 系统会自动弹窗显示Passkey
// 两端用户确认数字一致后,配对完成
小提示:createBond()方法调用后,系统会通过广播通知配对状态。建议注册BluetoothDevice.ACTION_BOND_STATE_CHANGED广播来监听结果。
8.2 BLE配对:Just Works与Numeric Comparison
BLE(低功耗蓝牙)的配对机制和经典蓝牙不太一样。它更轻量,但也更灵活。常用的两种方式是Just Works和Numeric Comparison。
8.2.1 Just Works
Just Works,顾名思义,就是「直接工作」。配对过程中没有用户交互,两端设备自动完成密钥交换。
这种方式的好处是快,无感。但坏处也很明显——容易受到中间人攻击(MITM)。
什么时候用Just Works?我个人建议:
- 传输的数据不敏感(比如温度传感器读数)
- 设备没有显示屏或输入能力
- 对连接速度要求极高
警告:千万不要在涉及支付、门禁、医疗数据的场景中使用Just Works。我曾经见过一个智能门锁项目,为了省事用了Just Works,结果被安全团队直接打回重做。
8.2.2 Numeric Comparison
Numeric Comparison是BLE中安全性较高的配对方式。两端设备各自计算出一个6位数字,用户确认数字一致后,配对完成。
流程如下:
- 设备A发起配对,携带自己的公钥
- 设备B响应,携带自己的公钥
- 两端各自计算确认值(Confirm Value)
- 交换确认值,验证通过后显示6位数字
- 用户确认数字一致,完成配对
你想想看,这个过程中即使有中间人篡改数据,两端计算出的数字也会不同,用户一眼就能发现异常。
Android中配置Numeric Comparison的代码:
// 在GATT服务端设置配对方式
BluetoothGattServer gattServer = ...;
BluetoothDevice device = ...;
// 设置安全级别为高,触发Numeric Comparison
gattServer.setPreferredPhy(device,
BluetoothDevice.PHY_LE_1M_MASK,
BluetoothDevice.PHY_LE_1M_MASK,
BluetoothDevice.PHY_OPTIONS_NO_PREFERRED);
// 系统会自动根据IO能力选择配对方式
// 如果两端都有显示屏和确认按钮,就会走Numeric Comparison
关键点:配对方式的选择取决于两端设备的IO能力(输入输出能力)。Android系统会根据BluetoothDevice的IO能力自动选择最合适的配对方式。
8.3 绑定键的存储与删除
配对完成后,系统会把生成的密钥(Link Key或LTK)存储起来。下次连接时,直接使用存储的密钥完成认证,不需要重新配对。
这就是绑定的本质。
8.3.1 存储位置
在Android系统中,绑定键存储在以下几个位置:
| 存储位置 | 说明 | 访问方式 |
|---|---|---|
| Bluetooth数据库 | 系统级存储,位于/data/misc/bluetooth/ | 需要root权限 |
| Settings数据库 | 存储已配对设备列表 | 通过BluetoothAdapter API |
| 应用私有存储 | 应用可以自行存储设备地址等信息 | 普通文件读写 |
我记得有一次调试一个诡异的问题:设备明明已经配对了,但每次连接都要重新配对。后来发现是系统蓝牙数据库文件损坏了。嗯,这种情况只能清除蓝牙数据再重新配对。
8.3.2 删除绑定键
删除绑定键有两种方式:
- 解除配对:调用removeBond()方法,系统会删除存储的密钥
- 清除蓝牙数据:在设置中清除蓝牙应用数据,会删除所有配对信息
代码示例:
// 解除配对
BluetoothDevice device = ...;
try {
Method removeBondMethod = device.getClass().getMethod("removeBond");
removeBondMethod.invoke(device);
} catch (Exception e) {
e.printStackTrace();
}
// 监听解除配对结果
// 广播:BluetoothDevice.ACTION_BOND_STATE_CHANGED
// 状态变为:BOND_NONE
注意:removeBond()是隐藏API,需要通过反射调用。不同Android版本的实现可能有差异,建议做好兼容性处理。
8.3.3 绑定键的生命周期
绑定键不是永久有效的。以下情况会导致绑定键失效:
- 用户主动解除配对
- 设备恢复出厂设置
- 蓝牙协议栈数据被清除
- 密钥过期(某些安全策略下)
我曾经遇到一个案例:某款智能手表,每次手机系统升级后都要重新配对。排查后发现是系统升级时清除了蓝牙数据库,但手表端还保留着旧的绑定信息。解决方案是在手表端增加一个超时机制,如果一段时间内没有成功连接,自动清除本地绑定键。
8.4 本章小结
配对与绑定是蓝牙通信的基础。经典蓝牙的PIN/Passkey配对适合需要用户确认的场景,BLE的Just Works适合低安全要求的快速连接,Numeric Comparison则提供了更高的安全性。
绑定键的存储和删除看似简单,但实际开发中容易踩坑。我的建议是:
- 配对成功后,主动检查绑定状态
- 提供手动解除配对的入口
- 做好异常情况下的重试机制
好了,这一章的内容就到这里。下一章我们会聊蓝牙连接的管理,包括多设备连接、连接优先级、断线重连等实战问题。
公众号:蓝海资料掘金营,微信deep3321