BTHome logo

Encryption

BTHome is offering the option to use encrypted BLE advertisements to send your data. BTHome supports AES encryption (CCM mode) which works with a pre-shared key. When encrypted, the data can only be read by knowing the encryption key. The encryption key should be a 16 bytes long key (32 characters).

Encrypting your messages

We will demonstrate the encryption process with an example. Let's say you want to encrypt a temperature and humidity measurement, which are, in non-encrypted BTHome format 02CA09 03BF13

The input

First, we need to have the BTHome Device Information byte and the UUID.

We also need an Encryption Key, which can be used to decode the data later. The Encryption Key should be 16 bytes long, e.g. key = 231d39c1d7cc1ab1aee224cd096db932.

We also need a Counter, which is a 4 bytes long value (unsigned 32-bit little-endian integer), which should increase every advertisement. This counter can be used to implement the replay protection features of AES-CCM, but has to be implemented on the receiving side (e.g. by verifying that the counter has increased compared to the previous counter value). In the example we use 0x00112233 as value for the counter.

Last thing we need is the MAC address of the sensor device, which is a 6 bytes long bytestring, e.g. 5448E68F80A5.

Encrypting the input

To encrypt this data, we first create a so called nonce by combining the following information in one bytestring.

nonce = 5448e68f80a5d2fc4100112233

Next, we can encrypt the nonce with the encryption key with the following command.

from Cryptodome.Cipher import AES

cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4)
ciphertext, mic = cipher.encrypt_and_digest(data)

This will return the Ciphertext (the encrypted data) and a 4 byte Message Integrity Check (MIC), which has to be appended to your BLE advertisement.

Note that in BTHome V1 cipher.update(b"\x11") is used to add a header, to have the same encryption format as Xiaomi sensors. In BTHome V2, this header is not used anymore, make sure you remove this from your sensor firmware code when upgrading to BTHome V2.

The final encrypted Service Data, which you can broadcast in your (encrypted) BLE advertisement, is composed as follows.

Service Data = d2fc41a47266c95f730011223378237214

Decrypting your messages

So, now we have encrypted our first message. But how to decrypt it? Let's assume you received the above service data d2fc41a47266c95f730011223378237214. Your sensor device will also broadcast its MAC address in the header. The only thing you need is the encryption key, which you should have written down when you encrypted the message.

First, break down the service data into the following parts.

With this information, we can recreate the nonce again.

nonce = 5448e68f80a5d2fc4100112233

With the Nonce, the MIC and the encryption key, we can decode the data with the following commands.

from Cryptodome.Cipher import AES

cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4)
decrypted_data = cipher.decrypt_and_verify(ciphertext, mic)

The result will be the decrypted data 02ca09 03bf13 like we had on the top of this page.

Example script

A detailed example script is provided to demonstrate the encryption and decryption.