4、加密与认证:AES-CCM加密、签名与数据完整性、白名单与地址解析

好,咱们进入BLE安全机制里最核心的一块。加密、签名、还有地址处理。说实话,很多开发者觉得BLE安全就是配个对、设个密码,其实远不止这些。你想想看,一个蓝牙门锁,如果加密没做好,别人在空气里抓包就能把门打开,那还得了?

我个人习惯把BLE安全分成三层来看:链路层加密保证数据不被偷看,签名保证数据没被篡改,地址策略保证设备不被跟踪。这三层缺一不可。今天咱们就把它们一个一个说透。

核心要点:BLE安全不是单一技术,而是加密(AES-CCM)、完整性(CMAC签名)、隐私(白名单+地址解析)三者的组合拳。缺一个,你的产品就可能被攻破。

BLE安全机制三层架构图 BLE 安全机制三层架构 AES-CCM 加密 链路层数据加密 CMAC 签名 数据完整性校验 白名单 + 地址解析 设备隐私保护 三者协同工作:加密保证机密性,签名保证完整性,地址策略保证隐私 任何一层缺失,攻击者都可能利用漏洞发起中间人攻击或设备跟踪 AES-CCM 参数 • 密钥长度:128 bit • 随机数(Nonce):13 字节 • MIC 长度:4 / 8 / 12 / 16 字节 • 分组大小:16 字节 CMAC 签名参数 • 签名长度:4 / 8 字节 • 基于 AES-128 算法 • 用于 ATT 数据签名 • 无需配对即可使用 地址解析参数 • IRK:128 bit 密钥 • 私有地址:可解析 / 不可解析 • 白名单:最多 64 个设备 • 地址刷新:15 分钟

4.1 AES-CCM 加密:BLE的看家本领

AES-CCM,全称是 Advanced Encryption Standard - Counter with CBC-MAC。名字挺长,但说白了就是两件事:用CTR模式加密数据,用CBC-MAC算一个校验码。两个合在一起,既保证了机密性,又保证了完整性。

我记得第一次在项目里实现BLE加密时,踩了一个坑——Nonce(随机数)的构造。BLE协议对Nonce的格式有严格规定:8字节的IV(初始化向量)加上4字节的分组计数器,再加上1字节的标志位。如果你随便填,加密出来的数据对方根本解不开。

实战经验:Nonce 在 BLE 里绝对不能重复使用。同一个密钥下,Nonce 重复 = 加密被破解。我习惯在每次连接建立时重新生成 IV,并且用硬件随机数发生器来保证真随机。

AES-CCM 在 BLE 里的工作流程是这样的:

  1. 密钥协商:配对过程中,双方通过 ECDH 或 Just Works 等方式协商出 LTK(长期密钥)。
  2. Nonce 生成:主设备生成 IV,从设备确认。双方各自维护分组计数器。
  3. 加密传输:每个数据包用 CTR 模式加密,同时计算 MIC(消息完整性校验码)。
  4. 接收验证:接收方用同样的密钥和 Nonce 解密,并验证 MIC。MIC 对不上,直接丢弃。

这里有个关键点——MIC 的长度。BLE 4.0/4.1 固定用 4 字节 MIC,但从 4.2 开始支持 8 字节、12 字节甚至 16 字节。长度越长,安全性越高,但开销也越大。我个人建议,如果你的产品对功耗不敏感,尽量用 8 字节 MIC。4 字节 MIC 在理论上存在碰撞风险,虽然概率很低,但安全设计讲究的是「纵深防御」。

MIC 长度 安全性等级 额外开销 推荐场景
4 字节 基础 低 低功耗传感器、Beacon
8 字节 中等 中 智能家居、可穿戴设备
12 字节 高 较高 医疗设备、工业控制
16 字节 最高 高 金融支付、高安全场景

4.2 签名与数据完整性:别让数据在路上被改

加密能防止数据被偷看,但防不了数据被篡改。你想想看,攻击者虽然看不懂加密数据,但他可以把加密数据包整个替换掉,或者重放一个旧的数据包。这时候就需要签名来保证完整性。

BLE 里用的签名算法是 AES-CMAC,本质上就是用 AES 算法算一个消息认证码。和加密不同的是,签名不需要 Nonce,也不需要分组计数器。它只依赖一个共享密钥——CSRK(连接签名解析密钥)。

我在项目中遇到过这样一个场景:一个蓝牙锁具,用户用手机开锁。如果只加密不签名,攻击者可以录制一次开锁的加密数据包,然后在用户离开后重放。加了签名之后,每个数据包都带有一个基于数据内容和 CSRK 算出来的签名值,重放的数据包签名对不上,锁具直接忽略。

注意:签名和加密是两个独立的安全机制。你可以只加密不签名,也可以只签名不加密,还可以两者都用。但我的建议是——永远不要只签名不加密。签名本身会暴露数据的某些特征,攻击者可以通过签名值推断数据内容。加密 + 签名才是完整方案。

签名的计算过程其实不复杂:

// 伪代码:AES-CMAC 签名计算
function calculateCMAC(key, data):
    // 1. 用密钥生成子密钥 K1 和 K2
    K1 = generateSubkey(key, 0x87)
    K2 = generateSubkey(key, 0x1B)
    
    // 2. 对数据进行填充(如果需要)
    paddedData = pad(data)
    
    // 3. 分块处理,每块 16 字节
    for each block in paddedData:
        if block is last block:
            if block length == 16:
                block = block XOR K1
            else:
                block = block XOR K2
        cipher = AES_ECB_encrypt(key, block XOR previousCipher)
        previousCipher = cipher
    
    // 4. 取前 4 或 8 字节作为签名
    return cipher[0:signatureLength]

嗯,这里要注意:签名长度在 BLE 里通常取 4 字节或 8 字节。4 字节签名用于低功耗场景,8 字节用于高安全场景。和 MIC 一样,签名越长越安全,但功耗和带宽开销也越大。

4.3 白名单与地址解析:让设备学会「隐身」

好,加密和签名解决了数据的安全问题,但还有一个容易被忽视的问题——设备隐私。你想想看,如果蓝牙设备每次广播都用固定的 MAC 地址,攻击者就可以跟踪这个设备的移动轨迹。你今天带着蓝牙耳机去公司、回家、去健身房,全被记录下来了。

BLE 的解决方案是私有地址。设备定期更换自己的 MAC 地址,让攻击者无法关联。但问题来了——如果地址一直在变,合法的连接设备怎么认出它?

这就是白名单 + 地址解析要解决的问题。

白名单,说白了就是一张「允许连接我」的设备列表。每个设备有一个固定的身份标识(Identity Address),白名单里存的就是这些身份地址。当设备使用私有地址时,接收方用 IRK(身份解析密钥)来解析这个私有地址,看它是否对应白名单里的某个设备。

地址解析的过程是这样的:

  1. 设备 A 用 IRK 和随机数生成一个可解析的私有地址。
  2. 设备 B 收到这个地址后,用自己存储的 IRK 列表逐个尝试解析。
  3. 如果某个 IRK 能成功解析出地址,说明设备 A 在白名单里。
  4. 如果所有 IRK 都解析失败,设备 B 直接忽略这个连接请求。

关键点:白名单和地址解析是配合使用的。白名单决定「谁可以连我」,地址解析决定「你是谁」。没有地址解析,白名单就形同虚设——因为设备地址一直在变,你根本不知道谁在连你。

我曾经在一个智能门锁项目里吃过亏。当时只配了白名单,没启用地址解析。结果发现,门锁每次广播时,手机都能连上,但门锁根本不知道是谁在连——因为手机每次都用不同的私有地址。后来加上地址解析,问题才解决。

白名单的配置方式有两种:

配置方式 说明 适用场景
静态白名单 在固件中预置设备列表 固定配对设备,如遥控器、传感器
动态白名单 通过配对过程动态添加 手机 App 配对后自动加入

我个人更推荐动态白名单。静态白名单虽然简单,但一旦设备需要更换,就得升级固件,用户体验很差。动态白名单配合配对流程,用户只需要在 App 上点一下「配对」,设备就自动加入白名单,方便得多。

最后说一个容易被忽略的细节——地址刷新周期。BLE 协议规定,私有地址至少每 15 分钟更换一次。但实际项目中,我建议根据场景调整:

  • 高隐私场景(如医疗设备):每 1-2 分钟更换一次。
  • 普通场景(如智能家居):每 15 分钟更换一次。
  • 低功耗场景(如 Beacon):每 30-60 分钟更换一次,甚至不更换(使用不可解析私有地址)。