feat(core/huya): 🎯 一号一环境生产模块 account_env (绑定+登录CLI) + 修复 device_profile HDID NameError

- core/huya/account_env.py: 账号↔设备环境一对一绑定 (幂等画像 + guid32/Hebe_D1-D5
  每账号终身一致字段, 来源 R36 解密真实指纹 JSON), 主流程 bind_and_login +
  CLI (python -m core.huya.account_env 账号 密码)
- device_profile.py: 修复 generate_profile 引用未定义 HDID 的 NameError; HDID32
  注释补 R15/R39 来源 (md5('5008_ver_'+k1), k1=datadiv 常量)
- 实弹验证: 环境指纹走注册链, 服务端签发 t2(180B)/t5(40hex) 
This commit is contained in:
yml2213
2026-08-29 12:53:58 +08:00
parent 9b259537c0
commit 9653dbe3ba
2 changed files with 162 additions and 2 deletions
+154
View File
@@ -0,0 +1,154 @@
"""账号 ↔ 设备环境 一对一绑定与登录(生产入口)。
目标: **一个账号密码 ↔ 一套设备环境**, 首次绑定后环境终身复用。
整体链路(每步"是什么/从哪来"见各函数与行内注释):
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 .device_profile import (
HDID32, # 登录帧 t1.t0 32hex 常量 (R15 公式结果, k1 来源见 R39; device_profile.py)
_load_db,
_save_db,
get_profile, # 幂等画像: 同账号永远复用同一套 (data/huya_device_profiles.json)
)
from .app_login import HuyaAppPasswordLogin
# ---------------------------------------------------------------------------
# 环境生成: 一号一套的"环境"定义
# ---------------------------------------------------------------------------
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 内容, 此字段为后续真实载荷构建预留
"""
env = get_profile(account, force_new=force_new)
db = _load_db()
record = dict(db.get(account) 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[account] = 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)。
"""
env = get_or_create_env(account, 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(account) or env)
record.setdefault("bound_at", int(time.time())) # 首次绑定时间
record["last_login"] = {
"ok": result.success,
"msg": result.message[:120],
"at": int(time.time()),
}
db[account] = 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:]))
+8 -2
View File
@@ -40,7 +40,9 @@ REAL_MODELS = [
("gionee", "GN9013", "GN9013,29,10", (720, 1560)), ("gionee", "GN9013", "GN9013,29,10", (720, 1560)),
] ]
HDID32 = "ed0db8334cadd236c00cadf7e11ab5a5" # 登录帧t1.t0 设备证书 (与 GUID32/doLaunch 无关系) # 登录帧 t1.t0 32hex 设备证书 (R15: md5("5008_13.4.22_"+k1); k1 见 R39 datadiv 常量;
# 换 app 版本时需按新 app_version 重算 — 与 GUID32/doLaunch 体系无关)
HDID32 = "ed0db8334cadd236c00cadf7e11ab5a5"
APP_VERSION = "13.4.22" APP_VERSION = "13.4.22"
SDK_VERSION = "1.0.80138" SDK_VERSION = "1.0.80138"
@@ -66,7 +68,11 @@ def generate_profile(model_pick=None) -> dict:
"width": str(w), "width": str(w),
"height": str(h), "height": str(h),
"device_id": _rand_sha1_hex(), "device_id": _rand_sha1_hex(),
"hdid": HDID, # hdid = 登录帧 t1.t0 的 32hex appSign (R15 定案):
# md5("5008_" + app_version + "_" + k1) = ed0db833...ab5a5
# k1 = 865a4924a40897ac1fcfe6b4c2cbb0e3 (libhydeviceid.so datadiv 内嵌常量, R39)
# → 是 app 版本级常量 (同版本所有真机同值), 非设备级, 离线可算
"hdid": HDID32,
# 注: 不含 safedeviceid —— 每次登录前由 dfp_register 注册链签发, # 注: 不含 safedeviceid —— 每次登录前由 dfp_register 注册链签发,
# 画像不再持久化固定令牌 (见模块注释)。 # 画像不再持久化固定令牌 (见模块注释)。
} }