File size: 4,077 Bytes
5db4eb7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | #!/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()) |