前言

(1)因为需要将 ESP32 利用 BTHome 接入 Home Assistant ,所以我需要先了解一下 BTHome 的数据格式。

BLE广播包格式

固定格式数据解析

(1) BTHome 是依赖 BLE 的广播功能进行的一个通用数据格式。因此,我们在学习 BTHome 之前,需要先简单了解一下 BLE 的广播格式。

  • preamble(1/2 byte) : 这个前导码可以用于同步通讯双方的时钟,为接受方提供正确的时间基准。如果是 2M PHY , 那么前导码为 2byte 。如果是 1M PHY , 那么前导码为 1byte
  • Access Address(4 byte) : 这个是广播设备的设备地址。
  • PDU Header(1 byte) : 这个用于指示当前广播数据的一些特性。
  • PDU Type(4 bit) : 这个用于指示什么类型的广播数据。
  • RFU(1 bit) : 留作将来使用。
  • ChSel(1 bit) : 如果广播设备支持 LE Channel Selection Algorithm #2 算法,该位会被置1。这位会影响 BLE 设备广播时使用的通道选择策略,使能该算法能提高广播包的传输成功率或降低能耗。
  • TxAdd(1 bit) : 如果广播设备为随机地址,该位置1。如果是公共地址,该位置0。
  • RxAdd(1 bit) : 如果扫描设备为随机地址,该位置1。如果是公共地址,该位置0。
  • Length(1 byte) : 有效载荷(Payload)数据长度。
  • Payload(1-255 byte) : 实际的有效载荷,后面进一步介绍。
  • CRC(3 byte) : 用于数据校验。

BTHome数据格式解析_android

有效载荷(Payload)数据解析

(1)现在我们看重点了,有效载荷(Payload)中分为一个一个的AD Structure,每一个AD Structure代表一种类型数据,例如设备地址发射功率设备地址等信息。
(2)AD Structure又可以拆分为如下三种数据:

  • Length(1 byte) : 说明AD Type + AD Data 的长度,单位是 byte
  • AD Type(1 byte) : 这个用于指示当前AD Structure数据是表示什么含义。其中需要注意,一定包含一个 0x01 类型的数据,该数据表示 Flag ,用于指示广播设备是对 BLE经典蓝牙是否支持。关于该更为详细的AD Type 定义,可以阅读 Assigned Numbers Document (PDF)2.3 Common Data Types章节。
  • AD Data( Length - 1 byte) : 这个就是实际的数据了。具体数据含义与AD Type有关。

BTHome数据格式解析_javascript_02

明文

明文格式

(1)现在拥有了上面的基础,我们来看看 BTHome 的数据格式应该是怎么样的呢?我们现在开始看下面的这段,假设我们有一个名字叫做 ADCBLE 设备广播 BTHome 数据,那么他的数据格式应当如下。

  • AD Structure 1 : 因为广播数据必须包含 0x01 数据,该数据表示当前设备为 LE 通用可发现模式且不支持经典蓝牙
  • AD Structure 2 : 0x09 表示当前数据为设备名字,通过 ASCII 码表我们可知,当前设备的名字为 ADC
  • AD Structure 3 : 接下来是 BTHome 数据。
  • 0x0A : 用于指示当前 AD Structure 长度。这个因 BTHome数据内容长度多少而改变,与之前的含义一样。
  • 0x16 : 表示当前数据是自定义的 16 位服务 UUID。正式因为 BLE 能够自定义服务,因此使其有较强的扩展性。从而能够有 BTHome 这种数据包。
  • 0xD2FC : Shelly 设备的制造商 Allterco RoboticsBTHome 赞助了这个 UUID。因此,当 BTHome 设备识别到是自定义的 16 位服务时候,就会看接下来的 2 byte 数据是否为 0xD2FC 如果是的说明当前数据为 BTHome 数据。
  • BTHome Device Information : 该数据主要用于指示当前设备的是否为加密数据是否定期更新数据BTHome 版本
  • BTHome DATA : 这个就是实际的 BTHome 数据了。

BTHome数据格式解析_数据_03

(2)BTHome Device Information的具体内容如下:

  • 加密标志位(Encryption flag) : bit0,这个参数用于指示当前设备是否为加密数据。0表示为非加密数据,1表示为加密数据。
  • 触发标志位(Trigger based device flag) : bit2,该位用于告知接收器该设备是发送定期数据更新还是仅在触发事件时发送数据。这对接收器来说可能是有用的信息,例如可以防止设备进入不可用状态。目前本人还没具体搞明白该位有啥用。
  • BTHome 版本 : bit 5-7,代表 BTHome 版本。目前仅允许使用 BTHome 版本 1 或 2,其中 2 是最新版本。
  • bit 1bit 3-4 : 保留

(3)BTHome DATA理解:假设我们有16位 UUID 数据0xD2FC4002C40903BF13 ,根据前面的介绍。我们知道这是一个 BTHome 的非加密数据。因此我们可以尝试进行解析该数据。首先剔除非BTHome DATA的数据,得到实际有效数据为 02C40903BF13BTHome 数据含义可以参考 BTHome BLE advertising in the BTHome v2 format链接。

  • 0x02 C409 : 0x02 表示温度数据,接下来的2个字节表示温度数据,单位为0.01℃。因为当前是 0xC409 ,而 BTHome 是小端存储因此实际数据应该是 0x09C4 ,转换为十进制就是 2500 ,因此当前温度为 25℃
  • 0x03 BF13 : 0x02 表示当前是湿度数据,系数为 0.01。同理,小段存储,当前实际数据应该是 0x13BF 转换为十进制就是 5055,因此当前湿度为50.55%

明文可能存在的问题

(1)假设当前存在一个 BLE 设备是一个按键。我每次通过按下按键来控制 LED 的开关。那么此时存在一个问题,因为 BLE 进行广播时,为了保证数据被对端设备接收到,一个相同的数据包会进行多次广播。因此,对端设备也会采取相应的过滤机制,当他收到一个数据包时,发现下一个数据包和当前数据包一样,那么就会过滤掉该数据包。
(2)这样就存在一个问题,我每次按键按下时,发送的数据包都是一样的,那么我的按键将永远只有第一次按下才能够被对端设备识别到。
(3)为了解决这个问题,我们可以采用 0x00 类型数据。例如当我第一次按下按键,数据为 0x0000 3A01,第二次按下数据为0x0001 3A01

BTHome数据格式解析_java_04


BTHome数据格式解析_ci_05

加密数据

加密数据介绍

(1)一般来说,我们发送数据可能希望只被特定设备接收到,其余设备即使接收到也无法获知当前设备含义,于是我们可以用上数据包加密。
(2)数据包加密存在两个好处:
<1>其余设备即使截取到当前广播信息,也无法获知数据含义。
<2>即使是相同的数据,因为时间戳不同,加密后每次数据也不一样。因此不会存在数据包被错误过滤问题。
(3)注意,加密数据的BTHome Device Informationbit 0要设置为1。

解密脚本

(1)如下为一个数据包解密脚本。

import binascii
from cryptography.hazmat.primitives.ciphers.aead import AESCCM

def decrypt_payload(payload: bytes, mic: bytes, key: bytes, nonce: bytes) -> bytes:
    """Decrypt payload."""
    cipher = AESCCM(key, tag_length=4)
    try:
        data = cipher.decrypt(nonce, payload + mic, None)
    except ValueError as error:
        print("Decryption failed:", error)
        return b''
    return data

def decrypt_aes_ccm(key: bytes, mac: bytes, data: bytes) -> bytes:
    """Decrypt AES CCM."""
    if len(data) > 15 and data[0] == 0xD2 and data[1] == 0xFC:
        pkt = data[: data[0] + 1]
        uuid = pkt[0:2]
        sw_version = pkt[2:3]
        encrypted_data = pkt[3:-8]
        count_id = pkt[-8:-4]
        mic = pkt[-4:]
        # nonce: mac [6], uuid16 [2], count_id [4] # 6+3+4 = 13 bytes
        nonce = b"".join([mac, uuid, sw_version, count_id])
        return decrypt_payload(encrypted_data, mic, key, nonce)
    else:
        print("Error: format packet!")
    return b''

def main():
    """Example to decrypt BTHome payload."""
    # Input encrypted data and key
    encrypted_data_hex = input("Enter encrypted BTHome data (hex): ")
    key_hex = input("Enter key (hex): ")
    mac_hex = input("Enter MAC address (hex): ")

    # Convert inputs to bytes
    key = binascii.unhexlify(key_hex)
    mac = binascii.unhexlify(mac_hex)
    data = binascii.unhexlify(encrypted_data_hex)

    # Decrypt the data
    decrypted_data = decrypt_aes_ccm(key=key, mac=mac, data=data)
    
    if decrypted_data:
        print("Decrypted Hex Data:", decrypted_data.hex())
    else:
        print("Decryption failed")

if __name__ == "__main__":
    main()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.

(2)文件保存方法

# 1. 创建一个 decrypt_bthome.py 脚本
vim decrypt_bthome.py
# 2. 输入i,将如上代码粘贴进来
# 3. 输入 :wq 保存
# 4. 将脚本设置为可执行文件
sudo chmod +x decrypt_bthome.py
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

(3)示例输入

# 执行脚本
python3 decrypt_bthome.py
# 输入加密后的 BTHome 数据
Enter encrypted BTHome data (hex): d2fc41a47266c95f730011223378237214
# 输入密钥
Enter key (hex): 231d39c1d7cc1ab1aee224cd096db932 
# 输入广播设备的 MAC 地址
Enter MAC address (hex): 5448E68F80A5
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

(4)示例输出

# 解密后的数据
Decrypted Hex Data: 02ca0903bf13
  • 1.
  • 2.

BTHome数据格式解析_javascript_06

参考

(1) BTHome官方解密示例脚本 (2) BTHome encryption (3) BTHome BLE advertising in the BTHome v2 format (5) Core Specification_v5.0 (6)C站:BLE学习(2):广播包报文格式详解 (7) Assigned Numbers Document (PDF)