#!/usr/bin/env python # -*- coding: utf-8 -*- """临时脚本(只读):解出本轮走查夹具(e2e-m3-flow.py 建的 taskMain)名册行身份证明文。 用途:L3-B 浏览器走查要在门禁界面里**手动输入姓名 + 身份证号**, 而明文只在建夹具的那一刻存在过(库里是 AES 密文 / 脱敏 / HMAC 索引)。 dev 的 AES 密钥在 application-dev.yml 里(提交进仓库的开发专用密钥),格式见 AesGcmCipher: Base64( IV(12) || ciphertext || tag(16) )。 **只读,不写库。** 走查完请连同夹具任务一起清掉(`python scripts/e2e-m3-flow.py --clean `)。 """ from __future__ import annotations import base64 import re import subprocess import sys from cryptography.hazmat.primitives.ciphers.aead import AESGCM if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8") TASK_ID = int(sys.argv[1]) if len(sys.argv) > 1 else 2311 DEV_YML = r"D:\IDEA_Project\xiaotutongxue\backend\xt-server\src\main\resources\application-dev.yml" TEXT = open(DEV_YML, encoding="utf-8").read() AES_KEY = base64.b64decode(re.search(r"aes-key:\s*(\S+)", TEXT).group(1)) CIPHER = AESGCM(AES_KEY) def psql(sql: str) -> list[list[str]]: p = subprocess.run( ["docker", "exec", "xt-pg", "psql", "-U", "fill", "-d", "filldb", "-A", "-F", "\t", "-P", "pager=off", "-t", "-c", sql], capture_output=True, ) if p.returncode != 0: raise SystemExit(p.stderr.decode("utf-8", "replace")) # ⚠️ 不能对整段输出 .strip() —— psql 的 -A 模式在**末尾字段为空**时会留下结尾制表符, # 整段 strip 会把它吃掉,于是"未绑定"的那一行少一列、解包直接报 # `ValueError: not enough values to unpack`(而这个报错看起来像解密失败)。 out = p.stdout.decode("utf-8", "replace") return [ln.split("\t") for ln in out.splitlines() if ln.strip()] def decrypt(cipher_b64: str) -> str: raw = base64.b64decode(cipher_b64.strip()) return CIPHER.decrypt(raw[:12], raw[12:], None).decode("utf-8") rows = psql(f""" SELECT id, real_name, COALESCE(id_card_no,''), COALESCE(id_card_mask,''), COALESCE(bind_user_id::text,'') FROM collect_roster WHERE deleted = 0 AND task_id = {TASK_ID} ORDER BY id """) print(f"任务 {TASK_ID} 的名册(走查用;只打印,不入库)") print("名册行 | 姓名 | 身份证(明文) | 脱敏 | 绑定用户") for r in rows: rid, name, enc = r[0], r[1], r[2] mask = r[3] if len(r) > 3 else "" bind = r[4] if len(r) > 4 else "" try: plain = decrypt(enc) except Exception as e: # noqa: BLE001 print(f"{rid} | {name} | [解不开:{type(e).__name__}] | {mask} | {bind or '未绑定'}") continue print(f"{rid} | {name} | {plain} | {mask} | {bind or '未绑定'}")