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.
-
BTHome Device Information:
- bit 0: “Encryption flag”
1
(encrypted) - bit 1-4: “Reserved for future use”
0000
(not used) - bit 5-7: “BTHome Version”
010
(version 2)
01000001
. After converting this to a byte, we get0x41
- bit 0: “Encryption flag”
- UUID:
0xFCD2
, reversed per byte we getD2FC
)
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.
- MAC address
5448E68F80A5
- UUID
D2FC
- BTHome device data byte
41
- Counter
00112233
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.
- UUID (reversed per byte)
D2FC
- BTHome device data byte
41
- Ciphertext
a47266c95f73
- Counter
00112233
- Message Integrity Check (MIC)
78237214
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.
- UUID: First 2 bytes, should be
d2fc
- BTHome device data: 3rd byte, should be
41
-
Ciphertext: 4th byte from the start till 8th byte from the end:
a47266c95f73
-
Counter: 8th byte from the end till 4th byte from the end:
00112233
-
Message Integrity Check (MIC): 4th byte from the end till the end:
78237214
With this information, we can recreate the nonce again.
- MAC address
5448E68F80A5
- UUID
d2fc
- BTHome device data byte
41
- Counter
00112233
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.