Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
Ernst79 opened this issue May 19, 2021 · 21 comments
Closed

Comments

@Ernst79
Copy link
Collaborator

Ernst79 commented May 19, 2021

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
Copy link

gogui63 commented May 21, 2021

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
Copy link
Collaborator Author

Ernst79 commented May 21, 2021

Did you try method 6 in the FAQ?

@rexbut
Copy link
Contributor

rexbut commented May 21, 2021

I succeeded to retrieve the beaconKey using ylkg08y.md and mikettle !!! The beaconKey has been encrypted with the token: rexbut/mikettle@62ca322#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
Copy link
Contributor

rexbut commented May 21, 2021

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

@Ernst79
Copy link
Collaborator Author

Ernst79 commented May 21, 2021

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
Copy link
Contributor

rexbut commented May 21, 2021

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
Copy link
Contributor

rexbut commented May 22, 2021

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
Copy link
Collaborator Author

Ernst79 commented May 22, 2021

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

@rezmus
Copy link

rezmus commented May 22, 2021

@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
Copy link
Contributor

rexbut commented May 22, 2021

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

@rexbut
Copy link
Contributor

rexbut commented May 22, 2021

Can anyone verify that the script works with YLKG07YL ?

@Busyrev
Copy link

Busyrev commented May 22, 2021

@rexbut trying it now. Will post here the result

@Busyrev
Copy link

Busyrev commented May 22, 2021

@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
Copy link
Collaborator Author

Ernst79 commented May 22, 2021

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
Copy link

Busyrev commented May 22, 2021

@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
Copy link
Collaborator Author

Ernst79 commented May 22, 2021

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
Copy link

Busyrev commented May 22, 2021

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

@Ernst79
Copy link
Collaborator Author

Ernst79 commented May 26, 2021

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

@Ernst79 Ernst79 closed this as completed May 26, 2021
@roeeklinger
Copy link

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
Copy link

genex89 commented Nov 30, 2022

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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants