安全与加密:BLE配对绑定、Just Works、Passkey Entry、OOB、加密数据传输
说到 BLE 的安全,我估计不少朋友第一反应就是「配对」。嗯,配对确实是安全的第一步,但它远不止「连上就行」那么简单。我在做第一个 BLE 项目时,就踩过配对的坑——设备连上了,数据也传了,结果发现传输的内容完全是明文,随便拿个抓包工具就能看到。那感觉,就像把家门钥匙挂在门外一样。
所以这一章,我们来把 BLE 的安全机制彻底捋清楚。说白了,就是回答三个问题:怎么配对?怎么绑定?怎么加密传数据?
1. BLE 安全模型概览
BLE 的安全模型分三层:
- 未加密通信:最原始的状态,谁都能监听。
- 加密通信:通过配对协商出密钥,之后的数据都加密传输。
- 绑定:把密钥存下来,下次连接直接复用,不用再配对。
你想想看,如果你的设备每次连接都要重新配对输入 PIN 码,用户体验得多糟糕。绑定就是为了解决这个问题的。
核心概念:配对是过程,绑定是结果。配对完成后如果双方都存储了密钥,就叫绑定。
2. 配对流程拆解
BLE 配对分三个阶段,我习惯叫它「三步走」:
- 配对特征交换:双方互相告诉对方「我支持什么安全等级、我有什么 IO 能力」。
- 短期密钥(STK)生成:根据双方能力,选择一种配对方法生成临时密钥。
- 传输密钥分发:用 STK 加密通道,把长期密钥(LTK)等分发给对方。
这里有个细节:IO 能力决定了配对方法的选择。比如你的手机有屏幕和键盘,你的智能手环只有一个小按钮,那它们就只能用 Just Works。
3. 三种配对方法详解
3.1 Just Works
Just Works,名字就告诉你「只管干,别问」。它不需要用户输入任何东西,也不需要确认任何数字。说白了,就是自动完成配对。
适用场景:
- 设备没有输入输出能力(比如传感器、灯泡)
- 对安全性要求不高的场景
安全风险:
- 无法防止中间人攻击(MITM)
- 因为不需要用户确认,攻击者可以悄悄配对
注意:Just Works 虽然叫「Just Works」,但它仍然会生成加密密钥,数据是加密传输的。只是它无法验证对方身份。我曾经在一个智能门锁项目里看到有人用 Just Works,吓得我赶紧建议改成了 Passkey Entry。
3.2 Passkey Entry
Passkey Entry 需要在一台设备上显示一个 6 位数字,在另一台设备上输入这个数字。双方都确认后,配对才完成。
工作流程:
- 设备 A 生成一个 6 位随机数(比如 123456)
- 设备 A 显示这个数字
- 用户在设备 B 上输入 123456
- 双方确认数字一致,配对继续
为什么能防 MITM? 因为中间人无法同时伪造两端的显示和输入。你想想看,攻击者要同时控制你的手机屏幕和键盘,这几乎不可能。
我的经验:Passkey Entry 在 Android 上实现时,要注意处理输入超时。我记得有一次用户输入太慢,导致配对失败,后来我把超时时间从 30 秒改成了 60 秒,问题就解决了。
3.3 OOB(Out of Band)
OOB 的意思是「带外」,也就是通过 BLE 之外的通道交换配对信息。最常见的例子就是用 NFC 碰一碰完成配对。
优势:
- 安全性最高,因为攻击者要同时攻破 BLE 和 OOB 通道
- 用户体验好,碰一下就行
实现方式:
- NFC:手机碰设备,交换密钥
- 二维码:扫描设备上的二维码获取密钥
- 声波:通过音频传输密钥(少见)
避坑指南:我曾经在 OOB 实现中犯过一个低级错误——把 OOB 数据直接明文写在二维码里。后来意识到,OOB 数据本身也应该加密,否则二维码被拍照就泄露了。
4. 加密数据传输
配对完成后,BLE 会使用 AES-128 加密所有后续数据。具体来说:
- 加密算法:AES-128-CCM
- 密钥长度:128 位
- 加密范围:所有 ATT 协议数据单元(PDU)
在 Android 端,你不需要手动处理加密。只要配对成功,系统会自动加密通信。你只需要在连接时指定安全等级:
// 设置安全等级为加密 + MITM 保护
bluetoothGatt.requestMtu(512)
bluetoothGatt.requestConnectionPriority(
BluetoothGatt.CONNECTION_PRIORITY_HIGH
)
// 启动安全连接
bluetoothDevice.createBond()
但要注意,createBond() 只是发起配对请求,具体用什么配对方法,由系统根据双方 IO 能力决定。
5. Android 上的配对实践
在 Android 上处理 BLE 配对,有几个关键点:
5.1 监听配对状态
// 注册广播接收器监听配对状态
val filter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
registerReceiver(bondStateReceiver, filter)
private val bondStateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val device = intent.getParcelableExtra<BluetoothDevice>(
BluetoothDevice.EXTRA_DEVICE
)
val bondState = intent.getIntExtra(
BluetoothDevice.EXTRA_BOND_STATE,
BluetoothDevice.BOND_NONE
)
when (bondState) {
BluetoothDevice.BOND_BONDING -> {
Log.d("BLE", "配对中...")
}
BluetoothDevice.BOND_BONDED -> {
Log.d("BLE", "配对成功")
// 可以开始加密通信了
}
BluetoothDevice.BOND_NONE -> {
Log.d("BLE", "未配对")
}
}
}
}
5.2 处理 Passkey Entry 输入
当系统选择 Passkey Entry 时,你需要监听 ACTION_PAIRING_REQUEST:
val filter = IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)
registerReceiver(pairingRequestReceiver, filter)
private val pairingRequestReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val device = intent.getParcelableExtra<BluetoothDevice>(
BluetoothDevice.EXTRA_DEVICE
)
val pairingVariant = intent.getIntExtra(
BluetoothDevice.EXTRA_PAIRING_VARIANT,
BluetoothDevice.PAIRING_VARIANT_PIN
)
if (pairingVariant == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) {
// 显示 6 位数字让用户确认
val passkey = intent.getIntExtra(
BluetoothDevice.EXTRA_PAIRING_KEY,
0
)
// 在 UI 上显示 passkey,让用户确认
showPasskeyDialog(passkey)
}
}
}
小技巧:如果你想让设备强制使用 Passkey Entry,可以在 GATT 服务中声明 IO 能力为 KeyboardOnly 或 DisplayYesNo。但要注意,这需要设备端也做相应配置。
6. 知识体系图
下面这张图总结了 BLE 安全的核心逻辑:
7. 常见问题与避坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 配对成功后数据还是明文 | 没有启用加密连接 | 检查 GATT 连接是否设置了 requestMtu 和 createBond |
| Passkey Entry 输入后配对失败 | 超时或数字不匹配 | 延长超时时间,确保两端数字一致 |
| OOB 配对不触发 | OOB 数据格式不对 | 检查 OOB 数据长度和哈希算法是否匹配 |
| 绑定后重连仍需配对 | 密钥未正确存储 | 检查设备端是否支持绑定,Android 端是否调用了 setDeviceKey |
重要提醒:不要为了省事而跳过配对。我曾经见过一个项目,为了「用户体验」直接跳过配对,结果数据被隔壁房间的人用抓包工具全看光了。安全不是可选项,是必选项。
8. 总结
BLE 安全说白了就是三件事:选对配对方法、处理好绑定、确保数据加密。Just Works 最方便但最不安全,Passkey Entry 适合有输入输出的设备,OOB 最安全但实现复杂。
我个人建议:能用 Passkey Entry 就别用 Just Works,能用 OOB 就别用 Passkey Entry。安全等级每提升一级,你的用户数据就多一层保护。
嗯,这一章的内容就到这里。记住,安全不是功能,是底线。