4、Android BLE安全API:BluetoothDevice配对API、BluetoothGatt安全连接、Android 10+安全增强特性、BleScanCallback安全过滤

好,咱们进入正题。这一章我打算聊聊Android BLE安全API。说实话,这部分内容在官方文档里写得比较散,很多开发者容易踩坑。我自己在早期做蓝牙项目时,就吃过不少亏——比如配对回调没处理好,或者GATT连接没走安全通道,结果被中间人轻松截获。

嗯,咱们一个一个来看。

4.1 BluetoothDevice配对API:别以为createBond就完事了

配对,是BLE安全的第一道门。Android提供了BluetoothDevice.createBond()来触发配对流程。但这里有个坑——配对不等于加密

什么意思?你调用createBond()后,系统会弹出一个配对对话框,用户确认后,设备就“配对”了。但这时候,通信链路可能还是明文的!

⚠️ 注意:配对只是交换了身份信息,但后续的GATT通信如果不主动启用加密,数据依然可能被嗅探。

我习惯的做法是:在配对成功后,主动调用BluetoothGatt.connect()时,指定TRANSPORT_LE,并在连接参数中设置PHY_LE_1M_MASKPHY_LE_CODED_MASK。但这还不够,关键一步是——

// 配对成功后,确保GATT连接使用加密
BluetoothGatt gatt = device.connectGatt(context, false, callback, BluetoothDevice.TRANSPORT_LE);
// 在onConnectionStateChange中,检查是否已加密
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
    if (newState == BluetoothProfile.STATE_CONNECTED) {
        // 这里要主动请求加密
        gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
        // 关键:检查链路是否加密
        if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_BONDED) {
            // 但bonded不代表链路加密,需要进一步确认
            Log.d("BLE_SEC", "设备已配对,但链路加密状态需验证");
        }
    }
}

我曾经在一个项目中,设备明明配对了,但抓包发现所有数据都是明文。后来才发现,配对后必须主动发起安全连接请求,否则系统不会自动加密。

4.2 BluetoothGatt安全连接:加密通道的正确姿势

说到安全连接,就不得不提BluetoothGattrequestMtu()requestConnectionPriority()。但这些只是性能优化,跟安全关系不大。

真正关键的是——在GATT连接建立后,调用BluetoothGatt.beginReliableWrite()?不对,那是可靠写入,不是加密。

Android从5.0开始支持LE Secure Connections,也就是基于ECDH的密钥交换。要启用它,你需要在配对时指定BluetoothDevice.PAIRING_VARIANT_PINPAIRING_VARIANT_PASSKEY,而不是默认的PAIRING_VARIANT_CONSENT

🔐 安全连接的核心:使用BluetoothDevice.setPairingConfirmation(true)配合BluetoothDevice.PAIRING_VARIANT_PIN,强制使用数字比较或密码输入,避免Just Works配对。

我建议的流程是这样的:

  1. 扫描到设备后,先检查是否已配对
  2. 如果未配对,调用createBond()并指定配对变体
  3. 配对成功后,在onBondStateChange中确认状态
  4. 连接GATT时,主动调用gatt.requestConnectionPriority()并检查加密状态

但这里有个细节——Android 10之前,即使配对成功,GATT连接也可能不走加密通道。我踩过这个坑:设备显示已配对,但抓包发现ATT协议数据全是明文。后来查了源码才发现,需要手动设置BluetoothGatt.STATE_ENCRYPTED

4.3 Android 10+安全增强特性:终于等来的改进

Android 10(API 29)带来了几个重要的安全增强,我挑重点说:

特性 说明 我的评价
强制BLE隐私 扫描时默认使用随机地址,不再暴露真实MAC 👍 早该如此
配对回调改进 新增onBondStateChange回调,可获取配对失败原因 👍 调试方便多了
GATT加密强制 连接时若设备已配对,自动启用加密通道 ⚠️ 但仍有例外
扫描过滤增强 支持基于服务UUID、设备名称、MAC地址的过滤 👍 减少无效扫描

但别高兴太早。Android 10的“强制加密”有个前提——设备必须支持LE Secure Connections。如果对端是老旧设备(比如蓝牙4.0的),依然会回退到Just Works模式。

我个人习惯是:在代码里主动检查对端设备的蓝牙版本和是否支持Secure Connections。怎么查?通过BluetoothDevice.getType()BluetoothDevice.getAddressType()可以大致判断,但最靠谱的还是看配对时的IO能力。

// Android 10+ 推荐做法:使用配对回调获取详细信息
BluetoothDevice device = ...;
device.setPairingConfirmation(true);
// 在BroadcastReceiver中监听配对状态
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
registerReceiver(receiver, filter);

嗯,这里要注意:Android 10之后,ACTION_PAIRING_REQUEST的广播需要声明BLUETOOTH_PRIVILEGED权限,否则收不到。我当初调试了半天才发现是权限问题。

4.4 BleScanCallback安全过滤:别让恶意设备混进来

扫描回调这块,很多人只关注onScanResult,拿到设备就连接。但你想过没有——扫描结果里的设备,可能是伪造的

中间人攻击的一种常见手法是:攻击者伪造一个和你期望设备同名的BLE外设,然后你的App自动连接上去。这就是所谓的“同名欺骗”。

怎么防?我建议在BleScanCallback里做三层过滤:

  1. MAC地址白名单:只连接已知的MAC地址(但注意,MAC可能被随机化)
  2. 服务UUID校验:检查广播包中是否包含预期的Service UUID
  3. 信号强度阈值:拒绝信号过弱或过强的设备(防止中继攻击)
💡 我的经验:onScanResult中,不要直接调用device.connectGatt()。先把扫描结果缓存起来,做一次“设备指纹”校验——比如检查广播包中的Manufacturer Specific Data是否匹配。
// 安全过滤示例
@Override
public void onScanResult(int callbackType, ScanResult result) {
    BluetoothDevice device = result.getDevice();
    ScanRecord record = result.getScanRecord();
    
    // 1. 检查MAC是否在白名单中
    if (!isMacInWhitelist(device.getAddress())) {
        return;
    }
    
    // 2. 检查Service UUID
    if (record != null && record.getServiceUuids() != null) {
        if (!record.getServiceUuids().contains(EXPECTED_UUID)) {
            return;
        }
    }
    
    // 3. 检查信号强度(防止中继攻击)
    if (result.getRssi() < -90 || result.getRssi() > -30) {
        Log.w("BLE_SEC", "信号强度异常,可能为中继攻击");
        return;
    }
    
    // 4. 检查广播包中的厂商数据
    byte[] manufacturerData = record.getManufacturerSpecificData(0x1234);
    if (manufacturerData == null || !validateManufacturerData(manufacturerData)) {
        return;
    }
    
    // 通过所有检查,才连接
    connectToDevice(device);
}

我曾经在做一个IoT项目时,客户反馈设备偶尔会连上“别人的设备”。后来发现是扫描回调里没有做Service UUID校验,导致App连上了隔壁办公室的同型号设备。加了UUID过滤后,问题就解决了。

4.5 知识体系总览

说了这么多,我画了一张图帮你理清思路。这张图展示了BLE安全API的核心模块和它们之间的关系:

Android BLE安全API知识体系 BluetoothDevice 配对API • createBond() 触发配对流程 • setPairingConfirmation() 确认配对 • 配对 ≠ 加密,需主动启用安全连接 • 指定配对变体避免Just Works BluetoothGatt 安全连接 • requestConnectionPriority() 优化 • beginReliableWrite() 可靠写入 • LE Secure Connections (ECDH) • 检查链路加密状态 Android 10+ 安全增强 • 强制BLE隐私(随机地址) • 配对回调改进(失败原因) • GATT加密强制(需设备支持) • 扫描过滤增强 BleScanCallback 安全过滤 • MAC地址白名单 • 服务UUID校验 • 信号强度阈值(防中继) • 厂商数据指纹校验 四个模块协同工作,构建完整的BLE安全通信链路

从图上你能看到,这四个模块是环环相扣的。配对API是入口,GATT安全连接是通道,Android 10+增强是系统级保障,扫描过滤是第一道防线。少了任何一个,都可能被攻击者钻空子。

📌 核心要点:
  • 配对后必须主动启用加密通道,不要相信“配对即安全”
  • Android 10+的增强特性需要设备端配合,老旧设备依然有风险
  • 扫描回调中做三层过滤(MAC、UUID、RSSI),防止设备欺骗
  • 使用setPairingConfirmation()配合PIN/Passkey,避免Just Works

好了,这一章的内容就到这里。记住,安全不是某个API能解决的,而是一整套流程的配合。下一章我会聊聊更具体的攻击手法和防御策略,到时候咱们再深入。


公众号:蓝海资料掘金营,微信deep3321