ds2api / scripts /shumei-re /decode_payload.py
luckfun233
feat: 更新数美 SDK 相关文档,添加复现手册及工具链,修复解析逻辑
5db4eb7
Raw History Blame Contribute Delete
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())