- core/huya: 新增 app_login, wup_encoder, nonce_forge, cert_forge, envelope_forge, device_profile, udb_aes - core/huya/__init__.py: 导出 login_huya_app_password 与 HuyaAppPasswordLogin - web/backend: 新增 /accounts/app-password-login 与 /accounts/app-password-login/selected 路由及 Schema,与原 Web 密码登录独立分开 - web/frontend: 增加 App 登录 API 与前端界面“App 登录选中”操作,弹窗结果明确区分 - tests: 新增 test_huya_app_login.py 单元测试覆盖全链路
79 lines
2.3 KiB
Python
79 lines
2.3 KiB
Python
"""虎牙证书 nonce(rnd) 本地生成器。
|
||
|
||
基于 XXTEA 算法与 uid + k1 派生密钥。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import struct
|
||
import time
|
||
|
||
K1_DEFAULT = "865a4924a40897ac1fcfe6b4c2cbb0e3"
|
||
|
||
|
||
def _xxtea_encrypt_words(v: list[int], k: list[int]) -> list[int]:
|
||
"""标准 XXTEA (delta=0x9e3779b9, rounds=6+52/n)。"""
|
||
n = len(v)
|
||
if n < 2:
|
||
return v
|
||
z = v[n - 1]
|
||
y = v[0]
|
||
s = 0
|
||
delta = 0x9E3779B9
|
||
q = 6 + 52 // n
|
||
while q > 0:
|
||
s = (s + delta) & 0xFFFFFFFF
|
||
e = (s >> 2) & 3
|
||
p = 0
|
||
while p < n - 1:
|
||
y = v[p + 1]
|
||
z = (v[p] + ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)))
|
||
^ ((s ^ y) + (k[(p & 3) ^ e] ^ z)))) & 0xFFFFFFFF
|
||
v[p] = z
|
||
p += 1
|
||
y = v[0]
|
||
z = (v[n - 1] + ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)))
|
||
^ ((s ^ y) + (k[((n - 1) & 3) ^ e] ^ z)))) & 0xFFFFFFFF
|
||
v[n - 1] = z
|
||
q -= 1
|
||
return v
|
||
|
||
|
||
def xxtea_encrypt(data: bytes, key16: bytes) -> bytes:
|
||
"""虎牙专用 XXTEA: 输出 words = ceil(len/4)+1, 末尾 word 存原始长度。"""
|
||
n = len(data)
|
||
nwords = (n // 4) + 1
|
||
v = [0] * nwords
|
||
for i in range(n // 4):
|
||
v[i] = struct.unpack("<I", data[i * 4:i * 4 + 4])[0]
|
||
v[nwords - 1] = n
|
||
k = [struct.unpack("<I", key16[i * 4:i * 4 + 4])[0] for i in range(4)]
|
||
_xxtea_encrypt_words(v, k)
|
||
return b"".join(struct.pack("<I", w & 0xFFFFFFFF) for w in v)
|
||
|
||
|
||
def pack_u64(v: int) -> bytes:
|
||
"""8字节小端裸值。"""
|
||
return struct.pack("<Q", v & 0xFFFFFFFFFFFFFFFF)
|
||
|
||
|
||
def gen_nonce(
|
||
uid: int,
|
||
k1: str = K1_DEFAULT,
|
||
service_time_ms: int | None = None,
|
||
counter: int = 0,
|
||
) -> bytes:
|
||
"""按设备算法生成 20B nonce(rnd)。
|
||
|
||
- uid: 虎牙 uid 数字(如 1199666914671)
|
||
- k1: 设备常量 32hex
|
||
- service_time_ms: 缺省为当前毫秒时间戳
|
||
- counter: 计数器(通常为 0)
|
||
"""
|
||
st = service_time_ms if service_time_ms is not None else int(time.time() * 1000)
|
||
st &= 0x7FFFFFFFFFFF
|
||
nonce_val = (counter & 0xFFFF) | (st << 16)
|
||
data = pack_u64(st) + pack_u64(nonce_val)
|
||
key = hashlib.md5((str(uid) + k1).encode()).hexdigest()[:16].encode()
|
||
return xxtea_encrypt(data, key)
|