92 lines
2.4 KiB
Python
92 lines
2.4 KiB
Python
"""虎牙 udb 登录证书 (biz_token) 铸造与解析工具。
|
|
|
|
证书格式:base64( [0x0c][key_idx][AES-128-ECB(key16, zeropad(P1))] )
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import os
|
|
import struct
|
|
|
|
from .udb_aes import udb_decrypt, udb_encrypt
|
|
|
|
KEY_TABLE_RAW = [
|
|
"4VYcPdvKKqjBHZtCmbroRXHk",
|
|
"xXEDWqiKLGwEZ6HubEiswCqK",
|
|
"3FMHubdKosFrhmXNLHTNHZwe",
|
|
]
|
|
DEFAULT_KEY16 = KEY_TABLE_RAW[0][:16].encode()
|
|
|
|
|
|
def build_p1(
|
|
app_id: bytes,
|
|
fingerprint: bytes,
|
|
cred: bytes,
|
|
rnd: bytes | None = None,
|
|
) -> bytes:
|
|
"""按样本结构组装 P1 明文。
|
|
|
|
- app_id: b'5008' 等 4 字节
|
|
- fingerprint: 40 字节 ASCII hex
|
|
- cred: 114 字节 hyCred
|
|
- rnd: 20 字节 nonce,缺省用 os.urandom(20)
|
|
"""
|
|
assert len(fingerprint) == 40, f"指纹应为40B, 实际为 {len(fingerprint)}"
|
|
assert len(cred) == 114, f"cred应为114B, 实际为 {len(cred)}"
|
|
r = os.urandom(20) if rnd is None else rnd
|
|
p1 = b"\x01\x04\x00" + app_id
|
|
p1 += struct.pack("<H", len(r)) + r
|
|
p1 += struct.pack("<H", len(fingerprint)) + fingerprint
|
|
p1 += struct.pack("<H", len(cred)) + cred
|
|
assert len(p1) == 187, f"P1长度应为187,实际为 {len(p1)}"
|
|
return p1
|
|
|
|
|
|
def parse_p1(data: bytes) -> dict:
|
|
"""解析 P1 结构。"""
|
|
o = 0
|
|
|
|
def tk(n: int) -> bytes:
|
|
nonlocal o
|
|
b = data[o : o + n]
|
|
o += n
|
|
return b
|
|
|
|
out = {
|
|
"head": tk(3).hex(),
|
|
"app_id": tk(4),
|
|
}
|
|
n = struct.unpack("<H", tk(2))[0]
|
|
out["rnd"] = tk(n)
|
|
n = struct.unpack("<H", tk(2))[0]
|
|
out["fingerprint"] = tk(n)
|
|
n = struct.unpack("<H", tk(2))[0]
|
|
out["cred"] = tk(n)
|
|
out["trailing"] = data[o:]
|
|
return out
|
|
|
|
|
|
def forge_cert(
|
|
p1: bytes,
|
|
key16: bytes = DEFAULT_KEY16,
|
|
key_idx: int = 0x20,
|
|
type_byte: int = 0x0C,
|
|
) -> bytes:
|
|
"""P1 -> 二进制证书 [type][key_idx][192B AES 密文]。"""
|
|
ct = udb_encrypt(key16, p1)
|
|
assert len(ct) == 192, len(ct)
|
|
return bytes([type_byte, key_idx]) + ct
|
|
|
|
|
|
def parse_cert(blob: bytes) -> tuple[int, int, bytes]:
|
|
"""[0x0c][idx][192B] -> (type, idx, ct)。"""
|
|
assert blob[0] == 0x0C and len(blob) >= 194, "非证书结构"
|
|
return blob[0], blob[1], blob[2:194]
|
|
|
|
|
|
def decrypt_cert(blob: bytes, key16: bytes = DEFAULT_KEY16) -> bytes:
|
|
"""解密二进制证书为 P1 明文。"""
|
|
_, _, ct = parse_cert(blob)
|
|
return udb_decrypt(key16, ct)
|