# -*- coding: utf-8 -*- """按 docs/tdc-案例.md §5.1 验证 collect 的「四段加密」结构(真机明文判决)。 要判决的问题: 项目现有 crypto.encrypt_string / tdc_collectgen.encrypt_plain 都是 「整体 JSON -> \x00 补齐到 8 -> 一次加密 -> 一个 base64」。 案例说「4 段独立加密,前 3 段空格 pad 到 24 字节对齐,sd 不 pad」。 两者只能有一个对 —— 用真机明文里的**空格填充串**来判。 用法: .venv/bin/python scripts/verify/verify_segment.py <已解密的 collect 明文文件> """ import json import re import sys if len(sys.argv) < 2: print("用法: verify_segment.py <已解密的 collect 明文文件>") print(" (明文来源: 用同会话 tdc.js 的密钥解密 TDC.getData(true) 的产出)") raise SystemExit(2) RAW = sys.argv[1] data = open(RAW, encoding="latin1").read() print("原始(含尾部 \\x00):", len(data), "字节") stripped = data.rstrip("\x00") print("去掉尾部 \\x00 后 :", len(stripped), "字节 (尾部 \\x00 个数 = %d)" % (len(data) - len(stripped))) print() runs = [(m.start(), m.end()) for m in re.finditer(r" {2,}", stripped)] print("[1] 连续空格串(只能来自「空格 pad」):") for a, b in runs: print(" 位置 %3d..%3d 长度 %2d" % (a, b, b - a)) print() # 段边界 = 每个空格串的结束位置(若空格串在段尾) bounds = [0] + [b for _a, b in runs[:-1]] if len(runs) > 1 else [0] # 更稳: 段边界取每个空格串的"起点所在段"的末尾 -> 直接用空格串终点 b1, b2, b3 = runs[0][1], runs[1][1], runs[2][1] segs = [("chunk1", 0, b1), ("trajectory", b1, b2), ("chunk2", b2, b3), ("sd", b3, len(stripped))] print("[2] 按空格串切出的 4 段:") for name, a, b in segs: s = stripped[a:b] print(" %-11s [%3d:%3d] len=%3d 24倍数=%-5s 8倍数=%-5s" % (name, a, b, len(s), len(s) % 24 == 0, len(s) % 8 == 0)) print() print("[3] 空格 pad 公式校验 (pad_len = 24 - len%24,总是补 1..24 个):") for name, a, b in segs[:3]: s = stripped[a:b] content = s.rstrip(" ") pad = len(s) - len(content) print(" %-11s 内容 %3d + pad %2d = %3d 公式给出 %2d -> %s" % (name, len(content), pad, len(s), 24 - len(content) % 24, "OK" if pad == 24 - len(content) % 24 else "FAIL")) print() print("[4] sd 段不 pad(只被 XTEA 的 8 字节块 \x00 补齐):") sd = stripped[b3:] print(" sd = %r" % sd) print(" sd 长度 %d (非 8 的倍数 -> 加密时被补 %d 个 \\x00)" % (len(sd), (8 - len(sd) % 8) % 8)) print(" 实际尾部 \\x00 个数 = %d" % (len(data) - len(stripped))) print() print("[5] 逻辑整体是否是合法 JSON,以及轨迹在 cd 里的位置:") obj = json.loads(stripped) cd = obj["cd"] traj_idx = [i for i, v in enumerate(cd) if isinstance(v, list) and v and all(isinstance(x, list) and len(x) == 3 for x in v)] print(" cd 槽数 = %d" % len(cd)) print(" 轨迹槽下标 = %s" % traj_idx) if traj_idx: print(" 轨迹内容前 3 点 = %s" % cd[traj_idx[0]][:3]) print() print("[6] 去 pad 后 4 段拼接 == 逻辑明文(应能解析成同一对象):") rebuilt = "".join(stripped[a:b].rstrip(" ") for _name, a, b in segs[:3]) + stripped[b3:] obj2 = json.loads(rebuilt) print(" 可解析为 JSON :", True) print(" 与原对象逐项相同 :", obj2 == obj) print(" 重建长度 %d (原文去尾 \\x00 后 %d, 差 = pad 总量 %d)" % (len(rebuilt), len(stripped), len(stripped) - len(rebuilt))) print() print("[7] 三段之间的连接处(验证 chunk1 以逗号结尾 / chunk2 以 '],' 结尾):") print(" chunk1 内容末尾 : %r" % stripped[0:b1].rstrip(" ")[-24:]) print(" trajectory 内容 : %r" % stripped[b1:b2].rstrip(" ")[:40]) print(" chunk2 内容开头 : %r" % stripped[b2:b3].rstrip(" ")[:24]) print(" chunk2 内容末尾 : %r" % stripped[b2:b3].rstrip(" ")[-24:]) print(" sd 内容 : %r" % stripped[b3:])