Last active
May 29, 2024 08:14
-
-
Save Doliman100/2c30251c048e9d01f33cbe4f00e2a341 to your computer and use it in GitHub Desktop.
Xiaomi Smart Band 8 request decryptor. Firmware downloader.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| requests | |
| pycryptodome | |
| charset-normalizer |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
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
Hello, I utilized this code to decrypt my Mi Band 7, but I've encountered an issue: I employed 'generate_enc_params' and 'generate_enc_signature' as separate functions. Then, using the same 'ssecurity', 'nonce', and 'params' from the captured packet, I attempted to generate the 'rc4_hash__'. However, I can't seem to get the same 'rc4_hash__' of the packet I captured. Could there be an error in my 'signature_string'? Your assistance would be greatly appreciated.