# -*- coding: utf-8 -*- """ XTEA 算法还原 + tdc.js 密钥提取 回归测试 (AGENTS.md §5.9) 固化的性质: 1. 标准 XTEA 加解密往返 2. 与真实 tdc.js **逐字节一致** (算法还原正确的判据, §3.0) 3. 插桩能从 tdc.js 复原密钥, 且密钥按构建固定 (§5.5 受控环境逐字节一致) 4. 模板标定 + 纯 Python 回填 的产物可被同一把密钥解回预期结构 运行: .venv/bin/python -m tcaptcha.tests.test_xtea_key """ from __future__ import annotations import base64 import json import struct import subprocess import sys import urllib.parse from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[2])) from tcaptcha.crypto import ( # noqa: E402 DEFAULT_KEY, generate_collect_from_template, recover_payload_template, xtea_decrypt_block, xtea_decrypt_bytes, xtea_encrypt_bytes, extract_xtea_key, ) ROOT = Path(__file__).resolve().parents[2] SAMPLE = ROOT / "tcaptcha" / "tdc_sample.js" SAMPLE_KEY = [1448563793, 1850559083, 1733513813, 1364613714] # 等价于 DEFAULT_KEY + 两个加盐 CD_PRE = b'{"cd":[[],"1920-1080-1055-24-*-*-|-*",0,0,"", ' # 旧版明文首段 (已补齐 48B) _fails: list[str] = [] def check(name: str, cond: bool, detail: str = "") -> None: print(f" [{'PASS' if cond else 'FAIL'}] {name}" + (f" {detail}" if detail else "")) if not cond: _fails.append(name) def run_tdc(script: str, points, options) -> dict: proc = subprocess.run( ["node", str(ROOT / "tcaptcha" / "tdc_worker.js"), "-", json.dumps(points), json.dumps(options)], input=script, capture_output=True, text=True, timeout=180) return json.loads(proc.stdout.strip().splitlines()[-1]) def test_xtea_roundtrip() -> None: print("1) 标准 XTEA 往返") key = list(SAMPLE_KEY) for chunk in (b"hello!!", bytes(range(32)), b"x" * 8): enc = xtea_encrypt_bytes(chunk, key) dec = xtea_decrypt_bytes(enc, key) check(f"往返 {len(chunk)}B", dec.startswith(chunk), f"{chunk[:8]!r}") # 已知明文 -> 与 tdc.js 密文逐字节一致 enc = xtea_encrypt_bytes(CD_PRE, key) check("加密结果可被同密钥解回", xtea_decrypt_bytes(enc, key).rstrip(b"\x00") == CD_PRE.rstrip(b"\x00")) def test_byte_exact_vs_tdcjs() -> None: print("2) 纯 Python 密文 == 真实 tdc.js 密文 (逐字节)") script = SAMPLE.read_text(encoding="utf-8", errors="replace") job = run_tdc(script, [[100, 100], [200, 200], [300, 300]], {"aid": "195672344"}) real = base64.b64decode(urllib.parse.unquote(job["collect"])) key = extract_xtea_key(script) check("提取到的密钥 == 已知 K'", key == SAMPLE_KEY, str(key)) # 旧版首段是常量 cd_pre, 可逐字节对拍 check("前 48 字节逐字节一致", xtea_encrypt_bytes(CD_PRE, key) == real[:48], xtea_encrypt_bytes(CD_PRE, key)[:8].hex()) def test_key_fixed_per_build() -> None: print("3) 密钥按构建固定 (同一脚本多次运行首块一致)") script = SAMPLE.read_text(encoding="utf-8", errors="replace") heads = set() for _ in range(2): job = run_tdc(script, [[10, 10], [20, 20], [30, 30]], {"aid": "195672344"}) heads.add(base64.b64decode(urllib.parse.unquote(job["collect"]))[:8]) check("两次运行首块相同", len(heads) == 1, heads.pop().hex()) def test_template_refill() -> None: print("4) 模板标定 + 纯 Python 回填") script = SAMPLE.read_text(encoding="utf-8", errors="replace") template, key = recover_payload_template(script) check("模板以 {\"cd\":[ 开头", template.startswith('{"cd":['), template[:24]) try: json.loads(template) check("模板是合法 JSON", True) except Exception as exc: # noqa: BLE001 check("模板是合法 JSON", False, str(exc)) collect = generate_collect_from_template(template, [(449, 254), (48, 53), (148, 252)], key) plain = xtea_decrypt_bytes(base64.b64decode(urllib.parse.unquote(collect)), key) \ .decode("latin1").rstrip(" ") check("回填产物可解回 {\"cd\":[ 开头", plain.startswith('{"cd":['), plain[:24]) check("回填产物以 }} 结尾", plain.rstrip().endswith("}}")) check("轨迹已换成 3 次点击的规模", len(plain) != len(template), f"{len(template)} -> {len(plain)}") def main() -> int: if not SAMPLE.exists(): print(f"缺少样本 {SAMPLE}") return 1 for fn in (test_xtea_roundtrip, test_byte_exact_vs_tdcjs, test_key_fixed_per_build, test_template_refill): fn() print() if _fails: print(f"❌ XTEA/密钥 回归失败 {len(_fails)} 项: {_fails}") return 1 print("✅ XTEA/密钥 回归全部通过") return 0 if __name__ == "__main__": raise SystemExit(main())