nccchirag / yeelight-ble-rotary-dimmer

YLKG08YL Yeelight bluetooth dimmer rotary switch protocol (#TODO reverse engineer) and hardware details.
68 stars 2 forks source link

Here is a little python script that shows the decryption. #2

Closed Mr-CherryLicker closed 1 year ago

Mr-CherryLicker commented 1 year ago
    Here is a little python script that shows the decryption. 

Your message starts at frame ctrl and stops before rssi.

from Cryptodome.Cipher import AES

data_string = "043e25020103008b98c54124f819181695fe5830b603368b98c54124f88bb8f2661351000000d6ef"
aeskey = "b853075158487ca39a5b5ea9"

#                                       frame dev ct ---mac------ ----encrypted payload- rssi
#                                       ctrl  id                  cipherpayld- -cnt-- tk 
#  043e25020103008b98c54124f819181695fe 5830 b603 36 8b98c54124f8 8bb8f2661351 000000 d6   ef

data = bytes(bytearray.fromhex(data_string))
key = bytes.fromhex(aeskey)

key_1 = key[0:6]
key_2 = bytes.fromhex("8d3d3c97")
key_3 = key[6:]
key = b"".join([key_1, key_2, key_3])
print("key: ", key.hex())

xiaomi_index = data.find(b'\x16\x95\xFE')
xiaomi_mac_reversed = data[xiaomi_index + 8:xiaomi_index + 14]
print("reversed mac: ", xiaomi_mac_reversed.hex())
# reversed mac: 8b98c54124f8

framectrl_data = data[xiaomi_index + 3:xiaomi_index + 5]
print("frame ctrl: ", framectrl_data.hex())
# frame ctrl: 5830

device_type = data[xiaomi_index + 5:xiaomi_index + 7]
print("device type (product id): ", device_type.hex())
# device type (product id): b603

encrypted_payload = data[xiaomi_index + 14:-1]
print("encrypted payload: ", encrypted_payload.hex())
# encrypted payload: 8bb8f2661351000000d6

packet_id = data[xiaomi_index + 7:xiaomi_index + 8]
payload_counter = b"".join([packet_id,  encrypted_payload[-4:-1]])
print("payload counter: ", payload_counter.hex())
# payload_counter: 36000000

nonce = b"".join([framectrl_data, device_type, payload_counter, xiaomi_mac_reversed[:-1]])
print("nonce: ", nonce.hex())
# nonce: 5830b603360000008b98c54124

aad = b"\x11"

token = encrypted_payload[-1:]
print("token: ", token.hex())
# token: d6

cipherpayload = encrypted_payload[:-4]
print("cipher payload: ", cipherpayload.hex())
# cipher payload: 8bb8f2661351

cipher = AES.new(key, AES.MODE_CCM, nonce=nonce, mac_len=4)
cipher.update(aad)

decrypted_payload = cipher.decrypt(cipherpayload)
print("decrypted payload: ", decrypted_payload.hex())
# decrypted payload:  01100300ff04

The decrypted payload can be read as follows. 0110 = Button (= type of message according to the MiBeacon protocol) 03 = length of data 00 = button ff = value 04 = press

button, value and press are the names I use in BLE monitor, depending on the device type, they are translated to a message. See the def obj0110(xobj): function in https://github.com/custom-components/ble_monitor/blob/master/custom_components/ble_monitor/ble_parser/xiaomi.py. In this example, press 04 + button = 0 means "rotate left" with (256 - 255(= ff) = 1 steps.

I also tried your BLE advertisement + beaconkey, but it doesn't seem to be right, I get this as result.

decrypted payload: ab330e5cbc82

Originally posted by @Ernst79 in https://github.com/nccchirag/yeelight-ble-rotary-dimmer/issues/1#issuecomment-846454507