Skip to content

Instantly share code, notes, and snippets.

@Doliman100
Last active May 29, 2024 08:14
Show Gist options
  • Select an option

  • Save Doliman100/2c30251c048e9d01f33cbe4f00e2a341 to your computer and use it in GitHub Desktop.

Select an option

Save Doliman100/2c30251c048e9d01f33cbe4f00e2a341 to your computer and use it in GitHub Desktop.
Xiaomi Smart Band 8 request decryptor. Firmware downloader.
requests
pycryptodome
charset-normalizer
import base64
import hashlib
import hmac
import json
import os
import random
import time
from getpass import getpass
from sys import platform
from urllib.parse import urlparse
import requests
from Crypto.Cipher import ARC4
if platform != "win32":
import readline
class XiaomiCloudConnector:
def __init__(self, username, password):
self._username = username
self._password = password
self._agent = self.generate_agent()
self._device_id = self.generate_device_id()
self._session = requests.session()
self._sign = None
self._ssecurity = None
self.userId = None
self._cUserId = None
self._passToken = None
self._location = None
self._code = None
self._serviceToken = None
def login_step_1(self):
url = "https://account.xiaomi.com/pass/serviceLogin?sid=miothealth&_json=true"
headers = {
"User-Agent": self._agent,
"Content-Type": "application/x-www-form-urlencoded"
}
cookies = {
"userId": self._username
}
response = self._session.get(url, headers=headers, cookies=cookies)
valid = response.status_code == 200 and "_sign" in self.to_json(response.text)
if valid:
self._sign = self.to_json(response.text)["_sign"]
return valid
def login_step_2(self):
url = "https://account.xiaomi.com/pass/serviceLoginAuth2"
headers = {
"User-Agent": self._agent,
}
fields = {
"sid": "miothealth",
"hash": hashlib.md5(str.encode(self._password)).hexdigest().upper(),
"callback": "https://sts-hlth.io.mi.com/healthapp/sts",
"qs": "%3Fsid%3Dmiothealth%26_json%3Dtrue",
"user": self._username,
"_sign": self._sign,
"_json": "true"
}
response = self._session.post(url, headers=headers, data=fields)
valid = response is not None and response.status_code == 200
if valid:
json_resp = self.to_json(response.text)
valid = "ssecurity" in json_resp and len(str(json_resp["ssecurity"])) > 4
if valid:
self._ssecurity = json_resp["ssecurity"]
self.userId = json_resp["userId"]
self._cUserId = json_resp["cUserId"]
self._passToken = json_resp["passToken"]
self._location = json_resp["location"]
self._code = json_resp["code"]
else:
if "notificationUrl" in json_resp:
print("Two factor authentication required, please use following url and restart extractor:")
print(json_resp["notificationUrl"])
print()
return valid
def login_step_3(self):
headers = {
"User-Agent": self._agent,
"Content-Type": "application/x-www-form-urlencoded"
}
response = self._session.get(self._location, headers=headers)
if response.status_code == 200:
self._serviceToken = response.cookies.get("serviceToken")
return response.status_code == 200
def login(self):
self._session.cookies.set("sdkVersion", "accountsdk-18.8.15", domain="mi.com")
self._session.cookies.set("sdkVersion", "accountsdk-18.8.15", domain="xiaomi.com")
self._session.cookies.set("deviceId", self._device_id, domain="mi.com")
self._session.cookies.set("deviceId", self._device_id, domain="xiaomi.com")
if self.login_step_1():
if self.login_step_2():
if self.login_step_3():
return True
else:
print("Unable to get service token.")
else:
print("Invalid login or password.")
else:
print("Invalid username.")
return False
def execute_api_call_encrypted(self, pathPrefix, url, params):
headers = {
"User-Agent": self._agent,
"region_tag": "cn",
"HandleParams": "true",
}
cookies = {
"cUserId": str(self._cUserId),
"serviceToken": str(self._serviceToken),
"locale": "en_us",
}
millis = round(time.time() * 1000)
nonce = self.generate_nonce(millis)
signed_nonce = self.signed_nonce(nonce)
fields = self.generate_enc_params(urlparse(url).path.removeprefix(pathPrefix), "POST", signed_nonce, nonce, params, self._ssecurity)
response = self._session.post(url, headers=headers, cookies=cookies, data=fields)
if response.status_code == 200:
decoded = self.decrypt_rc4(self.signed_nonce(fields["_nonce"]), response.text)
# return json.loads(decoded)
return decoded.decode('utf-8')
# return None
return response.text
def signed_nonce(self, nonce):
hash_object = hashlib.sha256(base64.b64decode(self._ssecurity) + base64.b64decode(nonce))
return base64.b64encode(hash_object.digest()).decode('utf-8')
@staticmethod
def signed_nonce_sec(nonce, ssecurity):
hash_object = hashlib.sha256(base64.b64decode(ssecurity) + base64.b64decode(nonce))
return base64.b64encode(hash_object.digest()).decode('utf-8')
@staticmethod
def generate_nonce(millis):
nonce_bytes = os.urandom(8) + (int(millis / 60000)).to_bytes(4, byteorder='big')
return base64.b64encode(nonce_bytes).decode()
@staticmethod
def generate_agent():
agent_id = "".join(map(lambda i: chr(i), [random.randint(65, 69) for _ in range(13)]))
return f"Android-7.1.1-1.0.0-ONEPLUS A3010-136-{agent_id} APP/xiaomi.smarthome APPV/62830"
@staticmethod
def generate_device_id():
return "".join(map(lambda i: chr(i), [random.randint(97, 122) for _ in range(6)]))
@staticmethod
def generate_signature(url, signed_nonce, nonce, params):
signature_params = [url.split("com")[1], signed_nonce, nonce]
for k, v in params.items():
signature_params.append(f"{k}={v}")
signature_string = "&".join(signature_params)
signature = hmac.new(base64.b64decode(signed_nonce), msg=signature_string.encode(), digestmod=hashlib.sha256)
return base64.b64encode(signature.digest()).decode()
@staticmethod
def generate_enc_signature(url, method, signed_nonce, params):
# signature_params = [str(method).upper(), url.split("com")[1].replace("/app/", "/")]
signature_params = [str(method).upper(), url]
for k, v in params.items():
signature_params.append(f"{k}={v}")
signature_params.append(signed_nonce)
signature_string = "&".join(signature_params)
return base64.b64encode(hashlib.sha1(signature_string.encode('utf-8')).digest()).decode()
@staticmethod
def generate_enc_params(url, method, signed_nonce, nonce, params, ssecurity):
params['rc4_hash__'] = XiaomiCloudConnector.generate_enc_signature(url, method, signed_nonce, params)
r = ARC4.new(base64.b64decode(signed_nonce))
r.encrypt(bytes(1024))
for k, v in params.items():
# params[k] = XiaomiCloudConnector.encrypt_rc4(signed_nonce, v)
params[k] = base64.b64encode(r.encrypt(v.encode())).decode()
params.update({
'signature': XiaomiCloudConnector.generate_enc_signature(url, method, signed_nonce, params),
'_nonce': nonce,
})
return params
@staticmethod
def to_json(response_text):
return json.loads(response_text.replace("&&&START&&&", ""))
@staticmethod
def encrypt_rc4(password, payload):
r = ARC4.new(base64.b64decode(password))
r.encrypt(bytes(1024))
return base64.b64encode(r.encrypt(payload.encode())).decode()
@staticmethod
def decrypt_rc4(password, payload):
r = ARC4.new(base64.b64decode(password))
r.encrypt(bytes(1024))
return r.encrypt(base64.b64decode(payload))
def hlthDecrypt(connector, nonce, data):
decoded = connector.decrypt_rc4(connector.signed_nonce(nonce), data)
print(decoded.decode('utf-8'))
def hlthHealthappRequest(connector):
url = "https://hlth.io.mi.com/healthapp/device/latest_ver?locale=en_us"
params = {
# "data": '{"app_level":"316000","channel":"prod","did":"641644679","fw_ver":"1.0.102","model":"miwear.watch.m66nfc","platform":"android"}'
"data": '{"app_level":"326000","channel":"prod","did":"641644679","fw_ver":"2.1.8","model":"miwear.watch.m66ql","platform":"android"}' # not work
}
result = connector.execute_api_call_encrypted("/healthapp", url, params)
print(result)
def hlthRequest(connector):
url = "https://hlth.io.mi.com/app/v1/version/check_upgrade?locale=en_us"
params = {
"data": '{"package_name":"com.mi.health","version_code":315004,"version_name":"3.15.4"}'
}
result = connector.execute_api_call_encrypted("", url, params)
print(result)
if __name__ == "__main__":
connector = XiaomiCloudConnector("[email protected]", "______")
logged = connector.login()
print(logged)
print(connector._ssecurity)
print(connector._serviceToken)
print(connector._cUserId)
print(connector._agent)
# manual
# connector = XiaomiCloudConnector("", "")
# connector._ssecurity = "xmvu7fpvhINdNLGUvekGBg=="
# connector._serviceToken = "wkPfCCglotJMhlsQ97aif6ufjv7ijW0zceFjQZk0idtM3XX35mK/z15o3ULwMwmRRnGm5wqjr3w1rxsOpBXhAV5p0hXWZvsYxOYpJTYpqt+tjxPT6dhCT7N6BGvTGAJytauNNWGpkogrImT2h18v3ZFGucLVCGzkRFbztXsAYY8="
# connector._cUserId = "A3nqXRz23I-8vfxB9oislDtrHWw"
# connector._agent = "Dalvik/2.1.0 (Linux; U; Android 10; Pixel Build/QP1A.191005.007.A3);3.16.0;316000"
# hlthDecrypt(connector, "QciAWjxJ8jcBrIAG", "ZodjjHqO3Kj7t+mqLdAErNeSM5PdOqxYoRbyENN7LNeVEKzFfh9APoYSuuKE45ceDJonTfS08sS7qFDsopycvFOufDc2WsxdCRDACC404gmNeIUAyLSNUFciX9ojV0/Q6F5Buh38xbbiC0dBO/cT9NS1kskPH0X9I5CnzeIW7g==")
hlthHealthappRequest(connector)
# hlthRequest(connector)
@Doliman100
Copy link
Author

I don't have any ideas what the problem is. Maybe something related to 'url' argument. Try make an issue in the original repository: https://github.com/PiotrMachowski/Xiaomi-cloud-tokens-extractor
I didn't research the application algorithms, I just adapted it to decrypt packets from com.mi.health app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment