"""账号 ↔ 设备环境 一对一绑定与登录(生产入口)。 目标: **一个账号密码 ↔ 一套设备环境**, 首次绑定后环境终身复用。 整体链路(每步"是什么/从哪来"见各函数与行内注释): 1. get_or_create_env(account) 生成/复用该账号专属设备环境 (幂等, 落盘) 2. HuyaAppPasswordLogin(...) 内部链: dfp 注册链(用本环境指纹) → 服务端签发 safedeviceid(t2) + device_id(t5) → WUP 密码登录 → cred(114B) → 全套 Cookie (风控滑块自动过验) 3. 绑定元数据回写 data/huya_device_profiles.json 为什么环境可以"一号一套"(算法依据, 均已破解定案): - 登录帧 t1.t0 (hdid32) 是 **app 版本级常量**, 与设备无关 (R15): md5("5008_13.4.22_" + k1) = ed0db8334cadd236c00cadf7e11ab5a5 k1 = 865a4924a40897ac1fcfe6b4c2cbb0e3 是 libhydeviceid.so datadiv 内嵌常量 (R39), 同 app 版本所有真机算出同一值 → 服务端无法用 t1.t0 区分设备, 离线永远算得对。 - 服务端真正区分设备靠注册链实时签发的 safedeviceid(t2)/device_id(t5) (2026-08-27 差分实验); 每账号环境固定 → 每次注册都等同"同一台设备换发令牌"。 - dfp 上报报文本体 = gzip + RC4(865a...abc798) (R36/R37), RC4 key 同为 APK 内嵌 常量 → 伪造报文与真机密码学不可区分; 当前注册链实测服务端不校验 cw 内容, 环境字段用于画像一致性, 不作为注册成败条件。 用法: python -m core.huya.account_env <账号> <密码> # 绑定(如未有)+登录 python -m core.huya.account_env <账号> <密码> --new-device # 抛弃旧环境, 换新设备 python -m core.huya.account_env <账号> --show # 只查看该账号绑定 """ from __future__ import annotations import hashlib import json import os import sys import time from .app_login import HuyaAppPasswordLogin from .device_profile import ( _load_db, _save_db, canonical_account_key, get_profile, # 幂等画像: 同账号永远复用同一套 (data/huya_device_profiles.json) ) # --------------------------------------------------------------------------- # 环境生成: 一号一套的"环境"定义 # --------------------------------------------------------------------------- def _rand_hex(n_bytes: int, salt: bytes) -> str: """随机 n_bytes 的 hex 串 (salt 仅用于区分字段用途, 服务端不校验)。""" return hashlib.sha256(os.urandom(n_bytes) + salt).hexdigest()[: n_bytes * 2] def get_or_create_env(account: str, force_new: bool = False) -> dict: """取回/首次生成该账号绑定的设备环境 (幂等)。 字段来源: - vendor/model/screen/width/height : 真机参数池 (device_profile.REAL_MODELS, 与金样本捕获真机 M2102J2SC 同构) - fingerprint (40hex) : CDID 形态设备指纹 (sha1 随机); 金样本 02df398797432eadefcc12767119ad5e80999389 即此形态; 注册链会把它替换进 selectOperator 请求 (dfp_register._select_operator_request) - device_id (40hex) : 画像侧占位; 登录实际用注册链签发的 t5 - hdid (32hex) : 登录帧 t1.t0 = R15 常量 (HDID32) - guid32 (32hex) : GUID 形态标识 (金样本 0a7dfaa8...; 出现于 dfp 指纹 JSON deviceinfo.guid, R36 解密样本), 每账号独立 - hebe (D1-D5, 32hex ×5) : dfp 指纹 JSON deviceinfo.Hebe_D1-D5 (R36 解密真实结构), 每账号独立生成终身复用 → 一号一设备的一致性来源; 当前注册链服务端不校验 cw 内容, 此字段为后续真实载荷构建预留 """ key = canonical_account_key(account) env = get_profile(key, force_new=force_new) db = _load_db() record = dict(db.get(key) or env) changed = False # guid32 / hebe: 一号一致字段, 首次生成后终身不变 if "guid32" not in record: record["guid32"] = _rand_hex(16, b"guid") changed = True if "hebe" not in record: record["hebe"] = { f"Hebe_D{i}": _rand_hex(16, f"hebe{i}".encode()) for i in range(1, 6) } changed = True if changed: db[key] = record _save_db(db) return record # --------------------------------------------------------------------------- # 绑定 + 登录: 主流程 # --------------------------------------------------------------------------- def bind_and_login( account: str, password: str, force_new_device: bool = False, proxies: dict | None = None, ) -> dict: """账号 ↔ 环境绑定并登录。 注册链在 login_cred_with_flow 内部执行一次 (register_device 用的 fingerprint 就是本环境的 40hex → 签发的 t2/t5 与环境绑定); safe_auth 滑块过验后的重发 沿用同一组设备字段 (app_login.login_cred_with_flow)。 """ key = canonical_account_key(account) env = get_or_create_env(key, force_new=force_new_device) print( f"[env] {account} ↔ {env.get('vendor')}/{env.get('model')} " f"fp40={env.get('fingerprint', '')[:12]}... guid32={env.get('guid32', '')[:12]}... " f"t1.t0={env.get('hdid', '')[:12]}..." ) # device_info 显式传入本环境 (否则 HuyaAppPasswordLogin 内部会再走一次 # get_profile —— 结果相同, 但显式传入让"环境→注册→登录"的数据流向可读); # 登录帧 device_id 用注册链本次签发的 t5 (login_cred_with_flow 内部完成替换) login_env = dict(env) login_env.setdefault("device_id", env.get("device_id")) result = HuyaAppPasswordLogin( account, password, proxies=proxies, device_info=login_env, ).login() # ---- 绑定元数据回写 (令牌不入库: t2/t5 每次登录实时签发, 环境才是长期身份) ---- db = _load_db() record = dict(db.get(key) or env) record.setdefault("bound_at", int(time.time())) # 首次绑定时间 record["last_login"] = { "ok": result.success, "msg": result.message[:120], "at": int(time.time()), } db[key] = record _save_db(db) return { "account": account, "env": { "model": env.get("model"), "vendor": env.get("vendor"), "fingerprint": env.get("fingerprint"), "guid32": env.get("guid32"), "hdid_t1t0": env.get("hdid"), }, "login_success": result.success, "login_message": result.message, "code": getattr(result, "code", None), } # --------------------------------------------------------------------------- # CLI # --------------------------------------------------------------------------- def main(argv: list[str]) -> int: args = [a for a in argv if not a.startswith("--")] if "--show" in argv and args: print(json.dumps(get_or_create_env(args[0]), indent=2, ensure_ascii=False)) return 0 if len(args) < 2: print(__doc__) return 2 out = bind_and_login(args[0], args[1], force_new_device="--new-device" in argv) print(json.dumps(out, indent=2, ensure_ascii=False)) return 0 if out["login_success"] else 1 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))