custom-components / ble_monitor

BLE monitor for passive BLE sensors
https://community.home-assistant.io/t/passive-ble-monitor-integration/
MIT License
1.91k stars 247 forks source link

Encryption key Yeelight remotes (YLYK01YL) and dimmers (YLKG07YL and YLKG08YL) #353

Closed Ernst79 closed 3 years ago

Ernst79 commented 3 years ago

There are currently two ways described in the FAQ to get the encryption key for the Yeelight Remote (YLYK01YL) and Yeelight Dimmers (YLKG07YL and YLKG08YL). If you have easier ways to get the encryption key, please report in this issue.

I will later copy some relevant info from #289

gogui63 commented 3 years ago

Do you know how to get the encryption key without ceiling light ? I do not have any BT Xiaomi gateway and I can't connect to the dimmer directly

Ernst79 commented 3 years ago

Did you try method 6 in the FAQ?

rexbut commented 3 years ago

I succeeded to retrieve the beaconKey using ylkg08y.md and mikettle !!! The beaconKey has been encrypted with the token: https://github.com/rexbut/mikettle/commit/62ca322c28ebef529b79e04a197999388bbf5d23#diff-5af407bf9ddc11b895e682c5e87562faaf8fa639563795be12af8049765d03b7R239

$ python3 demo.py connect F8:24:41:C5:98:8B 950
DEBUG:mikettle.mikettle:Init Mikettle with mac F8:24:41:C5:98:8B and pid 950
Connect
Authenticating
INFO:mikettle.mikettle:firmware_version: 1.0.1_1
INFO:mikettle.mikettle:beaconkey: b853075158487ca39a5b5ea9
rexbut commented 3 years ago

@Ernst79 Do you think you can automate the recovery or do I have to write a script?

Ernst79 commented 3 years ago

Well done. I think for now, it's the easiest to make a little script and a short explanation how to use it, I guess. I can put is somewhere here in the BLE monitor repo if you want and add the explanation in the FAQ. Just post it here and I will take care of it (or make a PR).

Perhaps later I can look into automating it, but that won't be shortly (as BLE monitor isn't designed to connect to anything, it just listens passively (by design 😄).

rexbut commented 3 years ago

It would be nice to add it to this repo.

Source: https://github.com/rexbut/mikettle/blob/master/get_beacon_key.py

I tested with "YLKG08YL"

#!/usr/bin/env python3

# Usage:
#   pip3 install bluepy
#   python3 get_beacon_key.py <MAC> <PRODUCT_ID>
# 
# List of PRODUCT_ID:
#   339: For 'YLYK01YL'
#   950: For 'YLKG07YL' and 'YLKG08YL'
#
# Example: 
#   python3 get_beacon_key.py AB:CD:EF:12:34:56 950

from bluepy.btle import UUID, Peripheral, DefaultDelegate

import random
import re
import sys

MAC_PATTERN = r"^[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2}$"

UUID_SERVICE = "fe95"

HANDLE_AUTH = 3
HANDLE_FIRMWARE_VERSION = 10
HANDLE_AUTH_INIT = 19
HANDLE_BEACON_KEY= 25

MI_KEY1 = bytes([0x90, 0xCA, 0x85, 0xDE])
MI_KEY2 = bytes([0x92, 0xAB, 0x54, 0xFA])
SUBSCRIBE_TRUE = bytes([0x01, 0x00])

def reverseMac(mac) -> bytes:
    parts = mac.split(":")
    reversedMac = bytearray()
    leng = len(parts)
    for i in range(1, leng + 1):
        reversedMac.extend(bytearray.fromhex(parts[leng - i]))
    return reversedMac

def mixA(mac, productID) -> bytes:
    return bytes([mac[0], mac[2], mac[5], (productID & 0xff), (productID & 0xff), mac[4], mac[5], mac[1]])

def mixB(mac, productID) -> bytes:
    return bytes([mac[0], mac[2], mac[5], ((productID >> 8) & 0xff), mac[4], mac[0], mac[5], (productID & 0xff)])

def cipherInit(key) -> bytes:
    perm = bytearray()
    for i in range(0, 256):
        perm.extend(bytes([i & 0xff]))
    keyLen = len(key)
    j = 0
    for i in range(0, 256):
        j += perm[i] + key[i % keyLen]
        j = j & 0xff
        perm[i], perm[j] = perm[j], perm[i]
    return perm

def cipherCrypt(input, perm) -> bytes:
    index1 = 0
    index2 = 0
    output = bytearray()
    for i in range(0, len(input)):
        index1 = index1 + 1
        index1 = index1 & 0xff
        index2 += perm[index1]
        index2 = index2 & 0xff
        perm[index1], perm[index2] = perm[index2], perm[index1]
        idx = perm[index1] + perm[index2]
        idx = idx & 0xff
        outputByte = input[i] ^ perm[idx]
        output.extend(bytes([outputByte & 0xff]))

    return output

def cipher(key, input) -> bytes:
    # More information: https://github.com/drndos/mikettle
    perm = cipherInit(key)
    return cipherCrypt(input, perm)

def generateRandomToken() -> bytes:
    token = bytearray()
    for i in range(0, 12):
        token.extend(bytes([random.randint(0,255)]))
    return token

def get_beacon_key(mac, product_id):
    reversed_mac = reverseMac(mac)
    token = generateRandomToken()

    # Pairing
    input(f"Activate pairing on your '{mac}' device, then press Enter: ")

    # Connect
    print("Connection in progress...")
    peripheral = Peripheral(deviceAddr=mac)
    print("Successful connection!")

    # Auth (More information: https://github.com/archaron/docs/blob/master/BLE/ylkg08y.md)
    print("Authentication in progress...")
    auth_service = peripheral.getServiceByUUID(UUID_SERVICE)
    auth_descriptors = auth_service.getDescriptors()
    peripheral.writeCharacteristic(HANDLE_AUTH_INIT, MI_KEY1, "true")
    auth_descriptors[1].write(SUBSCRIBE_TRUE, "true")
    peripheral.writeCharacteristic(HANDLE_AUTH, cipher(mixA(reversed_mac, product_id), token), "true")
    peripheral.waitForNotifications(10.0)
    peripheral.writeCharacteristic(3, cipher(token, MI_KEY2), "true")
    print("Successful authentication!")

    # Read
    beacon_key = cipher(token, peripheral.readCharacteristic(HANDLE_BEACON_KEY)).hex()
    firmware_version = cipher(token, peripheral.readCharacteristic(HANDLE_FIRMWARE_VERSION)).decode()

    print(f"beaconKey: '{beacon_key}'")
    print(f"firmware_version: '{firmware_version}'")

def main(argv):
    # ARGS
    if len(argv) <= 2 : 
        print("usage: get_beacon_key.py <MAC> <PRODUCT_ID>\n")
        print("PRODUCT_ID:")
        print("  339: For 'YLYK01YL'")
        print("  950: For 'YLKG07YL' and 'YLKG08YL'")
        return

    # MAC
    mac = argv[1].upper()
    if not re.compile(MAC_PATTERN).match(mac):
        print(f"[ERROR] The MAC address '{mac}' seems to be in the wrong format")
        return

    # PRODUCT_ID  
    product_id = argv[2]
    try:
        product_id = int(product_id)
    except Exception:
        print(f"[ERROR] The Product Id '{product_id}' seems to be in the wrong format")
        return

    # BEACON_KEY
    get_beacon_key(mac, product_id)

if __name__ == '__main__':
    main(sys.argv)
rexbut commented 3 years ago

The script works with "YLYK01YL" by setting PRODUCT_ID = 339:

# python3 get_beacon_key_2.py f8:24:41:e9:50:74
Activate pairing on your 'F8:24:41:E9:50:74' device, then press Enter:
Connection in progress...
Successful connection!
Authentication in progress...
Successful authentication!
beaconKey: '471342543805f83c2caa9deb'
firmware_version: '1.0.1_1'
Ernst79 commented 3 years ago

Thanks. I’ll add instructions in FAQ, will use this as the preferred solution for legacy encryption.

rezmus commented 3 years ago

@rexbut nice! you can add pid to cmd line params after mac.

btw: this method (maybe with small adjustments per pid) can be probably used to auth/read key from any mijia ble device.

rexbut commented 3 years ago

I just updated the script to take the PRODUCT_ID as a parameter.

rexbut commented 3 years ago

Can anyone verify that the script works with YLKG07YL ?

Busyrev commented 3 years ago

@rexbut trying it now. Will post here the result

Busyrev commented 3 years ago

@rexbut Tested with Yeelight Wireless Smart Dimmer Model: YLKG07YL Product Date:0806 It works

# python3 get_beacon_key.py -my-mac-here- 950
Activate pairing on your '-my-mac-here-' device, then press Enter: 
Connection in progress...
Successful connection!
Authentication in progress...
Successful authentication!
beaconKey: '-my-key-here-'
firmware_version: '1.0.1_1'
Ernst79 commented 3 years ago

I've added instructions in the FAQ.

The updated script is placed here.

I added the other remotes as well, including one that will be added in a next release.

Busyrev commented 3 years ago

@Ernst79 I`m not using Home Assistant, but custom scripts for my home automation. Is there a simple way to run ble-monitor scripts to just output to stdout decrypted messages from dimmer?

Ernst79 commented 3 years ago

No, currently not. There have been requests to make it available as a pypi package, but I didn't have time to do that. But the main decoding is done in \custom_components\ble_monitor\ble_parser\xiaomi.py. You could use aioblescan to get the raw BLE messages and write a script that does the parsing of these messages.

Busyrev commented 3 years ago

Installed HA, added device and it works. It shows actions from dimmer. Full chain works great.

Ernst79 commented 3 years ago

I'm closing this issue, the script is placed in the repository and FAQ has been updated.

roeeklinger commented 2 years ago

Hey,

I have a brand new YLKG07YL dimmer from Amazon.com, I have tested it with the script but it just hangs after Authentication in progress....

It seems to hang on line 111: auth_service = peripheral.getServiceByUUID(UUID_SERVICE)

I tested it manually with bluepy:

>>> conn = Peripheral("F8:24:41:C5:13:7F")
>>> conn.getCharacteristics()

It also seems to hang, once connected, the orange light on the dimmer stops, then it sleeps for around 2-3 seconds, then it blinks again once and dies.

Did anyone else encounter this?

genex89 commented 1 year ago

Hey,

I have a brand new YLKG07YL dimmer from Amazon.com, I have tested it with the script but it just hangs after Authentication in progress....

It seems to hang on line 111: auth_service = peripheral.getServiceByUUID(UUID_SERVICE)

I tested it manually with bluepy:

>>> conn = Peripheral("F8:24:41:C5:13:7F")
>>> conn.getCharacteristics()

It also seems to hang, once connected, the orange light on the dimmer stops, then it sleeps for around 2-3 seconds, then it blinks again once and dies.

Did anyone else encounter this?

Hi, did you solve it? I also have the dimmer in its new version and with the script I can't extract the key, it gives me an error after the connection.

breakjia commented 1 year ago

Hi, did you solve it? I also have the new version of the dimmer and using the script to extract to 16 bit key also does not work