Download scripts/shumei-re/decode_payload.py from a3216/ds2api: direct link, hf CLI and curl.
- Browser
- Download file 4.08 kB
-
https://huggingface.co/spaces/a3216/ds2api/resolve/main/scripts/shumei-re/decode_payload.py
- Command line
-
hf download hf://spaces/a3216/ds2api/scripts/shumei-re/decode_payload.py
-
curl -L -o decode_payload.py https://huggingface.co/spaces/a3216/ds2api/resolve/main/scripts/shumei-re/decode_payload.py
4.08 kB
| #!/usr/bin/env python3 | |
| """解密并校验数美 deviceprofile 请求体(payload.json) | |
| 用途: | |
| 1. 用 uid 派生 priId 解密 data,还原明文指纹对象(对照 capture_payload.js 的输出) | |
| 2. 校验完整性字段 er/tn:按 JS 语义重算拼接串并比对 | |
| 3. 校验各字段的 DES 明文(可选,需提供密钥表) | |
| 依赖:pip install pycryptodome | |
| 用法: | |
| python decode_payload.py payload.json --uid 88888888-8888-4888-8888-888888888888 [--dump] | |
| """ | |
| import argparse | |
| import base64 | |
| import gzip | |
| import hashlib | |
| import json | |
| import sys | |
| try: | |
| from Crypto.Cipher import DES | |
| except ImportError: # pragma: no cover | |
| print("需要 pycryptodome: pip install pycryptodome", file=sys.stderr) | |
| sys.exit(1) | |
| AES_IV = "0102030405060708" | |
| def aes_cbc_decrypt_hex(pri_id: str, data_hex: str) -> bytes: | |
| from Crypto.Cipher import AES | |
| raw = bytes.fromhex(data_hex) | |
| dec = AES.new(pri_id.encode(), AES.MODE_CBC, AES_IV.encode()).decrypt(raw) | |
| return dec.rstrip(b"\x00") | |
| def des_decrypt_b64(key: str, value: str) -> str: | |
| raw = base64.b64decode(value) | |
| out = DES.new(key.encode(), DES.MODE_ECB).decrypt(raw) | |
| return out.rstrip(b"\x00").decode("utf-8", "replace") | |
| def js_value_string(v) -> str: | |
| """复刻 JS 中 ``'' + value`` 的字符串化行为。""" | |
| if isinstance(v, bool): | |
| return "true" if v else "false" | |
| if isinstance(v, dict): | |
| return "[object Object]" # 嵌套对象不递归 | |
| if isinstance(v, list): | |
| return ",".join("" if x is None else js_value_string(x) for x in v) | |
| if v is None: | |
| return "null" | |
| return str(v) | |
| def compute_tn(obj: dict) -> str: | |
| """复刻 SDK 的 tn:按键排序拼接值,数字 ×10000(键名与分隔符不参与)。""" | |
| parts = [] | |
| for k in sorted(obj.keys()): | |
| v = obj[k] | |
| if isinstance(v, bool): | |
| parts.append(js_value_string(v)) | |
| elif isinstance(v, (int, float)): | |
| parts.append(str(int(v * 0x2710))) | |
| else: | |
| parts.append(js_value_string(v)) | |
| return hashlib.md5("".join(parts).encode("utf-8")).hexdigest() | |
| def main() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("payload", help="capture_payload.js 产出的 payload.json") | |
| ap.add_argument("--uid", required=True, help="签发时的 uid(priId = md5(uid)[:16])") | |
| ap.add_argument("--dump", action="store_true", help="打印全部明文字段") | |
| ap.add_argument("--er-key", default="2j48yowr", help="er/tn 字段的 DES 密钥(默认当前版本)") | |
| ap.add_argument( | |
| "--undefined-key", | |
| action="append", | |
| default=None, | |
| help="值为 undefined、只在 tn 里以 \"undefined\" 出现的字段(默认 yu,可重复传)", | |
| ) | |
| args = ap.parse_args() | |
| payload = json.load(open(args.payload, encoding="utf-8")) | |
| pri_id = hashlib.md5(args.uid.encode()).hexdigest()[:16] | |
| b64 = aes_cbc_decrypt_hex(pri_id, payload["data"]) | |
| obj = json.loads(gzip.decompress(base64.b64decode(b64))) | |
| print("解密成功:字段数 =", len(obj), "| protocol =", obj.get("protocol")) | |
| er = obj.get("er") | |
| if er: | |
| got_tn = des_decrypt_b64(args.er_key, er) | |
| # JSON 会丢弃值为 undefined 的字段,但 tn 拼接里仍以字符串 "undefined" 参与。 | |
| undefined_keys = args.undefined_key if args.undefined_key is not None else ["yu"] | |
| tn_input = {k: v for k, v in obj.items() if k != "er"} | |
| for k in undefined_keys: | |
| tn_input.setdefault(k, "undefined") | |
| calc_tn = compute_tn(tn_input) | |
| print("er 解密 =", got_tn) | |
| print("本地重算 =", calc_tn) | |
| print("tn 校验:", "一致 ✅" if got_tn == calc_tn else "不一致 ❌(拼接语义或字段集合有差异)") | |
| else: | |
| print("payload 中没有 er 字段(旧版本协议可能是明文 tn/nb)") | |
| if args.dump: | |
| for k in sorted(obj.keys()): | |
| print("%-16s %s" % (k, js_value_string(obj[k])[:100])) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) |