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

Xiaomi Temp Sensor v2 (LYWSD03MMC) BLE Decryption (Stock firmware) #107

Open
rmeissne opened this issue Dec 24, 2024 · 1 comment
Open

Comments

@rmeissne
Copy link

Hi Team,

Thanks for your work on those BLE features. I have the following sensor:

1:46:53 PM: Connecting to: LYWSD03MMC
1:46:58 PM: Hardware Revision String: B1.4
1:46:58 PM: Software Revision String: 0130
1:46:58 PM: Firmware Revision String: 1.0.0_0130

I have activated the sensor and set the bind key. My bind key: 8ef97ff4130915b0e7c0b9ca56962ae9. I am using the stock firmware. No, I do not want to use any other firmware.

I am using some Shelly devices as a BLE proxy. I receive he advertised BLE data and I forward it HTTP post to some server (python script). I am receiving the base64 encrypted data, I decrypt (base64) it.

I do receive the following data (without the spaces, I just added them to group the data as far as I know how to interpret it):

020106 1a16 95fe 5858 5b05 05 2fcfff38c1a4d57308db90000000e38dd476

I would like to write some python script to decode this data an later get the temp/humidity/battery values. I had a look at:
https://github.com/Ernst79/bleparser/blob/c42ae922e1abed2720c7fac993777e1bd59c0c93/package/bleparser/xiaomi.py

However, it is not really clear for me what data is used to properly AES decrypt.

  • This is fixed (020106) - flags
  • 1a16 (this is the length 1a (26), 0x16 = Service Data - 16-bit UUID
  • 95fe - UUID fe95

Now, I am not so 100% sure if this is correct, but looks like it does make sense:

  • 5858 (frame control bytes?) which translate to 0101100001011000 (bit 4 means encryption used, bit 5 = mac in data)
  • as per the 2 x control bytes, this is apparently version 5 (frctrl_version = frctrl >> 12 # version)
  • 0101100001011000 - shift >> 12 -> 0101 (5)
  • 5b05 -> this is 0x055b ( 0x055B: "LYWSD03MMC" - this is the device type)
  • 05 <- this seems to be the packet ID (1 bytes)
  • 2fcfff38c1a4 - this is the MAC address reversed, as per the control frame it is included in the announcement (a4:c1:38:ff:cf:2f)

We are left with the following bytes:

  • d57308db90000000e38dd476

What is now the encrypted data to decrypt (d57308db90000000)? Is the MIC e38dd476? Why are there so many zeros? Is this really part of the encrypted data? Apparently there is some counter value as well, not sure if this is used here.

  • What would be now my nounce for this example please?

  • As per the code I have seen, the nounce is calculated ( nonce = b"".join([xiaomi_mac[::-1], data[6:9], data[-7:-4]]))

  • Part of the nounce is the reversed MAC, which would be: 2fcfff38c1a4

  • What would be the other correct values for this example please?

  • nounce: 2fcfff38c1a4 + what? - what is data[6:9], what data[-7:-4] please?

  • token which is the MIC is (token = data[-4:]) - this is now d476 or e38dd476?

  • cipherpayload? (cipherpayload = data[i:-7]) - not sure what i is in the code - what would be the value for this example please?

First step would be do decrypt the string before to parse for the transmitted values.

Thanks for your help. I appreciate your time.

Regards,
Robert

@rmeissne
Copy link
Author

I figured it out. If somebody is interested:

020106 1a16 95fe 5858 5b05 41 2fcfff38c1a4 5a3eeff41a 010000 9fe3bde7

bind_key = bytes.fromhex("8ef97ff4130915b0e7c0b9ca56962ae9")

2fcfff38c1a4 5b0541 010000

nonce = bytes.fromhex("2fcfff38c1a45b0541010000")
encrypted_payload = bytes.fromhex("5a3eeff41a")
mic = bytes.fromhex("9fe3bde7")

print("Bind-Key: ", bind_key)
print("Nonce: ", nonce)
print("Encrypted Payload: ", encrypted_payload)
print("MIC: ", mic)

cipher = AES.new(bind_key, AES.MODE_CCM, nonce=nonce, mac_len=4)
cipher.update(b"\x11")
data = cipher.decrypt_and_verify(encrypted_payload, mic)

print("Cleartext: ", data.hex())

Result would be: 061002ef01

This would be: 1006 length 02, value 01ef (humidity: 495 / 10 = 49.5%

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

1 participant