#!/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())