358 lines
11 KiB
Python
358 lines
11 KiB
Python
"""KWSG 10418 `__NS_sig3` helpers."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import hmac
|
||
import time
|
||
import zlib
|
||
|
||
from .enc_data import kwsg_10400_ecb_encrypt, load_kwsg_10400_tables
|
||
|
||
|
||
KWSG_10418_PREFIX_HEAD2 = bytes.fromhex("4151")
|
||
KWSG_10418_SIG3_PREFIX_CODE = 0x27
|
||
KWSG_10418_SIGN_PREFIX_CODE = 0x02
|
||
KWSG_10418_SAMPLE_SESSION_SEED = 0x0610FDA7
|
||
KWSG_10418_SIG3_PREFIX8 = bytes.fromhex("41512700a7fd1006")
|
||
KWSG_10418_SIGN_PREFIX8 = bytes.fromhex("41510200a7fd1006")
|
||
KWSG_10418_PREFIX8 = KWSG_10418_SIG3_PREFIX8
|
||
KWSG_10418_DEFAULT_STATE_SOURCE = 0xC001000000000000
|
||
KWSG_10418_SIG3_HMAC_KEY = (
|
||
b"hnna6SanFd1n43zLjCRrdyvLqhyJBw4Ao8NpRcEfixVHSueTmOJDao4KsUDS2nkP"
|
||
)
|
||
KWSG_10418_HMAC_KEY = KWSG_10418_SIG3_HMAC_KEY
|
||
|
||
|
||
def _input_to_bytes(value: str | bytes | bytearray) -> bytes:
|
||
if isinstance(value, str):
|
||
return value.encode("utf-8")
|
||
return bytes(value)
|
||
|
||
|
||
def kwsg_10418_session_seed_from_time(unix_time: int) -> int:
|
||
"""复现 native ``srand(time); rand() + 1`` 的进程 seed。"""
|
||
|
||
# Android bionic 沿用 BSD random 的 128-byte TYPE_3 状态和 310 次预热。
|
||
seed = int(unix_time) & 0xFFFFFFFF
|
||
state = [seed or 1]
|
||
for index in range(1, 31):
|
||
state.append((16807 * state[index - 1]) % 0x7FFFFFFF)
|
||
state.extend((state[0], state[1], state[2]))
|
||
for index in range(34, 345):
|
||
state.append((state[index - 31] + state[index - 3]) & 0xFFFFFFFF)
|
||
return ((state[344] >> 1) & 0x7FFFFFFF) + 1
|
||
|
||
|
||
def kwsg_10418_prefix8(
|
||
prefix_code: int = KWSG_10418_SIG3_PREFIX_CODE,
|
||
session_seed: int = KWSG_10418_SAMPLE_SESSION_SEED,
|
||
) -> bytes:
|
||
"""构造 `10418` digest pre-buffer 的前 8 字节。"""
|
||
code = int(prefix_code) & 0x0FFF
|
||
seed = int(session_seed) & 0xFFFFFFFF
|
||
return KWSG_10418_PREFIX_HEAD2 + code.to_bytes(2, "little") + seed.to_bytes(4, "little")
|
||
|
||
|
||
def load_kwsg_10418_tables(
|
||
t1_path: str = "out/kwsg_10418_B_T1.bin",
|
||
t2_path: str = "out/kwsg_10418_B_T2.bin",
|
||
) -> tuple[bytes, bytes]:
|
||
"""加载 `10418 innerFlag=false` / `0x26e9c` 所需 B 表。"""
|
||
return load_kwsg_10400_tables(t1_path, t2_path)
|
||
|
||
|
||
def load_kwsg_10418_sign_tables(
|
||
t1_path: str = "bin/kwsg_10400_T1.bin",
|
||
t2_path: str = "bin/kwsg_10400_T2.bin",
|
||
) -> tuple[bytes, bytes]:
|
||
"""加载 `10418 innerFlag=true` / `0x266fc` 所需 A 表。"""
|
||
return load_kwsg_10400_tables(t1_path, t2_path)
|
||
|
||
|
||
def _kwsg_10418_tables_or_load(
|
||
t1: bytes | None,
|
||
t2: bytes | None,
|
||
) -> tuple[bytes, bytes]:
|
||
if t1 is None and t2 is None:
|
||
return load_kwsg_10418_tables()
|
||
if t1 is None or t2 is None:
|
||
raise ValueError("t1 and t2 must be provided together")
|
||
return t1, t2
|
||
|
||
|
||
def kwsg_10418_prehash32(
|
||
input_bytes: bytes,
|
||
hmac_key: bytes = KWSG_10418_HMAC_KEY,
|
||
) -> bytes:
|
||
"""移植 `0x1ddbc -> 0x1de14 -> 0x241f4 -> 0x21604`。"""
|
||
return hmac.new(hmac_key, input_bytes, hashlib.sha256).digest()
|
||
|
||
|
||
def kwsg_10418_binary48_from_prehash32(prehash32: bytes, t1: bytes, t2: bytes) -> bytes:
|
||
"""移植 `0x1df8c -> 0x1dfc4 -> 0x274fc -> 0x27534`。"""
|
||
if len(prehash32) != 32:
|
||
raise ValueError("10418 prehash must be exactly 32 bytes")
|
||
return kwsg_10400_ecb_encrypt(prehash32, t1, t2)
|
||
|
||
|
||
def kwsg_10418_binary48(
|
||
input_bytes: bytes,
|
||
t1: bytes,
|
||
t2: bytes,
|
||
hmac_key: bytes = KWSG_10418_HMAC_KEY,
|
||
) -> bytes:
|
||
"""由原始 `10418` input string 生成 48-byte binary stage。"""
|
||
return kwsg_10418_binary48_from_prehash32(
|
||
kwsg_10418_prehash32(input_bytes, hmac_key),
|
||
t1,
|
||
t2,
|
||
)
|
||
|
||
|
||
def kwsg_10418_state_low24_from_source(state_source: int) -> int:
|
||
"""移植 `0x462a0` 从运行期状态源抽取的 24-bit 状态字段。"""
|
||
x = int(state_source) & 0xFFFFFFFFFFFFFFFF
|
||
v = 0
|
||
v |= (x >> 57) & 0x02
|
||
v |= (x >> 61) & 0x01
|
||
v |= (x >> 58) & 0x04
|
||
v |= (x >> 53) & 0x10
|
||
v |= (x >> 54) & 0x20
|
||
v |= (x >> 44) & 0x40
|
||
v |= 0x0D00
|
||
return v & 0x00FFFFFF
|
||
|
||
|
||
def kwsg_10418_digest24_from_binary48(
|
||
binary48: bytes,
|
||
counter: int,
|
||
unix_time: int,
|
||
state_source: int = KWSG_10418_DEFAULT_STATE_SOURCE,
|
||
prefix8: bytes = KWSG_10418_PREFIX8,
|
||
) -> bytes:
|
||
"""由 `10418` 的 48-byte binary stage 重建最终 24-byte digest。"""
|
||
if len(binary48) != 48:
|
||
raise ValueError("10418 binary stage must be exactly 48 bytes")
|
||
if len(prefix8) != 8:
|
||
raise ValueError("10418 prefix8 must be exactly 8 bytes")
|
||
|
||
buf = bytearray()
|
||
buf += prefix8
|
||
buf += (int(counter) & 0xFFFFFFFF).to_bytes(4, "little")
|
||
buf += (zlib.crc32(binary48) & 0xFFFFFFFF).to_bytes(4, "little")
|
||
buf += (int(unix_time) & 0xFFFFFFFF).to_bytes(4, "little")
|
||
buf += kwsg_10418_state_low24_from_source(state_source).to_bytes(4, "little")
|
||
|
||
s = sum(buf[:23])
|
||
mask = (s if s <= 0xFF else -s) & 0xFF
|
||
buf[23] = mask
|
||
for i in range(23):
|
||
buf[i] ^= (mask ^ i) & 0xFF
|
||
return bytes(buf)
|
||
|
||
|
||
def kwsg_10418_digest24(
|
||
input_bytes: bytes,
|
||
counter: int,
|
||
unix_time: int,
|
||
t1: bytes,
|
||
t2: bytes,
|
||
state_source: int = KWSG_10418_DEFAULT_STATE_SOURCE,
|
||
prefix8: bytes = KWSG_10418_PREFIX8,
|
||
hmac_key: bytes = KWSG_10418_HMAC_KEY,
|
||
) -> bytes:
|
||
"""由原始 `10418` input string 生成最终 24-byte digest。"""
|
||
return kwsg_10418_digest24_from_binary48(
|
||
kwsg_10418_binary48(input_bytes, t1, t2, hmac_key),
|
||
counter,
|
||
unix_time,
|
||
state_source,
|
||
prefix8,
|
||
)
|
||
|
||
|
||
def kwsg_10418_sig3_hex(
|
||
input_value: str | bytes | bytearray,
|
||
counter: int,
|
||
unix_time: int | None = None,
|
||
t1: bytes | None = None,
|
||
t2: bytes | None = None,
|
||
state_source: int = KWSG_10418_DEFAULT_STATE_SOURCE,
|
||
prefix8: bytes | None = None,
|
||
session_seed: int = KWSG_10418_SAMPLE_SESSION_SEED,
|
||
hmac_key: bytes = KWSG_10418_SIG3_HMAC_KEY,
|
||
) -> str:
|
||
"""生成 `innerFlag=false` 时 Java 返回的 48hex。"""
|
||
if unix_time is None:
|
||
unix_time = int(time.time())
|
||
t1, t2 = _kwsg_10418_tables_or_load(t1, t2)
|
||
if prefix8 is None:
|
||
prefix8 = kwsg_10418_prefix8(KWSG_10418_SIG3_PREFIX_CODE, session_seed)
|
||
return kwsg_10418_digest24(
|
||
_input_to_bytes(input_value),
|
||
counter,
|
||
unix_time,
|
||
t1,
|
||
t2,
|
||
state_source,
|
||
prefix8,
|
||
hmac_key,
|
||
).hex()
|
||
|
||
|
||
def kwsg_10418_digest24_unmix(digest24: bytes | bytearray | str) -> dict:
|
||
"""反解析 `10418` 最终 24-byte digest,恢复扰动前字段。"""
|
||
if isinstance(digest24, str):
|
||
digest = bytes.fromhex(digest24)
|
||
else:
|
||
digest = bytes(digest24)
|
||
if len(digest) != 24:
|
||
raise ValueError("10418 digest must be exactly 24 bytes / 48 hex")
|
||
|
||
mask = digest[23]
|
||
pre = bytearray(24)
|
||
for i in range(23):
|
||
pre[i] = digest[i] ^ ((mask ^ i) & 0xFF)
|
||
pre[23] = 0
|
||
|
||
s = sum(pre[:23])
|
||
expected_mask = (s if s <= 0xFF else -s) & 0xFF
|
||
prefix8 = bytes(pre[:8])
|
||
return {
|
||
"prefix8": prefix8,
|
||
"prefix8_ok": prefix8[:2] == KWSG_10418_PREFIX_HEAD2 and prefix8[3] == 0,
|
||
"prefix_code": int.from_bytes(prefix8[2:4], "little") & 0x0FFF,
|
||
"session_seed": int.from_bytes(prefix8[4:8], "little"),
|
||
"counter": int.from_bytes(pre[8:12], "little"),
|
||
"crc32": int.from_bytes(pre[12:16], "little"),
|
||
"unix_time": int.from_bytes(pre[16:20], "little"),
|
||
"state_low24": int.from_bytes(pre[20:23], "little"),
|
||
"mask": mask,
|
||
"mask_ok": mask == expected_mask,
|
||
"pre": bytes(pre),
|
||
}
|
||
|
||
|
||
class Kwsg10418State:
|
||
"""管理 `10418` 的 session_seed 与全局 counter。"""
|
||
|
||
def __init__(
|
||
self,
|
||
session_seed: int = KWSG_10418_SAMPLE_SESSION_SEED,
|
||
counter: int = 0,
|
||
state_source: int = KWSG_10418_DEFAULT_STATE_SOURCE,
|
||
) -> None:
|
||
self.session_seed = int(session_seed) & 0xFFFFFFFF
|
||
self.counter = int(counter) & 0xFFFFFFFF
|
||
self.state_source = int(state_source) & 0xFFFFFFFFFFFFFFFF
|
||
|
||
@classmethod
|
||
def from_digest(
|
||
cls,
|
||
digest24: bytes | bytearray | str,
|
||
state_source: int = KWSG_10418_DEFAULT_STATE_SOURCE,
|
||
) -> "Kwsg10418State":
|
||
parsed = kwsg_10418_digest24_unmix(digest24)
|
||
if not parsed["mask_ok"] or not parsed["prefix8_ok"]:
|
||
raise ValueError("invalid 10418 digest")
|
||
return cls(parsed["session_seed"], parsed["counter"], state_source)
|
||
|
||
@classmethod
|
||
def from_reward_sign(
|
||
cls,
|
||
sign_hex64: str,
|
||
sdk_id: str,
|
||
state_source: int = KWSG_10418_DEFAULT_STATE_SOURCE,
|
||
) -> "Kwsg10418State":
|
||
from .reward_sign import kwsg_10418_reward_sign_to_digest_hex
|
||
|
||
return cls.from_digest(
|
||
kwsg_10418_reward_sign_to_digest_hex(sign_hex64, sdk_id),
|
||
state_source,
|
||
)
|
||
|
||
def observe_digest(self, digest24: bytes | bytearray | str) -> dict:
|
||
parsed = kwsg_10418_digest24_unmix(digest24)
|
||
if not parsed["mask_ok"] or not parsed["prefix8_ok"]:
|
||
raise ValueError("invalid 10418 digest")
|
||
self.session_seed = parsed["session_seed"]
|
||
self.counter = parsed["counter"]
|
||
return parsed
|
||
|
||
def observe_reward_sign(self, sign_hex64: str, sdk_id: str) -> dict:
|
||
from .reward_sign import kwsg_10418_reward_sign_to_digest_hex
|
||
|
||
return self.observe_digest(
|
||
kwsg_10418_reward_sign_to_digest_hex(sign_hex64, sdk_id)
|
||
)
|
||
|
||
def next_counter(self) -> int:
|
||
self.counter = (self.counter + 1) & 0xFFFFFFFF
|
||
return self.counter
|
||
|
||
def sig3_hex(
|
||
self,
|
||
input_value: str | bytes | bytearray,
|
||
unix_time: int | None = None,
|
||
t1: bytes | None = None,
|
||
t2: bytes | None = None,
|
||
) -> str:
|
||
return kwsg_10418_sig3_hex(
|
||
input_value,
|
||
self.next_counter(),
|
||
unix_time,
|
||
t1,
|
||
t2,
|
||
self.state_source,
|
||
session_seed=self.session_seed,
|
||
)
|
||
|
||
def reward_sign(
|
||
self,
|
||
input_value: str | bytes | bytearray,
|
||
sdk_id: str,
|
||
unix_time: int | None = None,
|
||
t1: bytes | None = None,
|
||
t2: bytes | None = None,
|
||
) -> str:
|
||
from .reward_sign import kwsg_10418_reward_sign
|
||
|
||
return kwsg_10418_reward_sign(
|
||
input_value,
|
||
sdk_id,
|
||
self.next_counter(),
|
||
unix_time,
|
||
t1,
|
||
t2,
|
||
self.state_source,
|
||
session_seed=self.session_seed,
|
||
)
|
||
|
||
|
||
__all__ = [
|
||
"KWSG_10418_DEFAULT_STATE_SOURCE",
|
||
"KWSG_10418_HMAC_KEY",
|
||
"KWSG_10418_PREFIX8",
|
||
"KWSG_10418_PREFIX_HEAD2",
|
||
"KWSG_10418_SAMPLE_SESSION_SEED",
|
||
"KWSG_10418_SIG3_HMAC_KEY",
|
||
"KWSG_10418_SIG3_PREFIX8",
|
||
"KWSG_10418_SIG3_PREFIX_CODE",
|
||
"KWSG_10418_SIGN_PREFIX8",
|
||
"KWSG_10418_SIGN_PREFIX_CODE",
|
||
"Kwsg10418State",
|
||
"kwsg_10418_binary48",
|
||
"kwsg_10418_binary48_from_prehash32",
|
||
"kwsg_10418_digest24",
|
||
"kwsg_10418_digest24_from_binary48",
|
||
"kwsg_10418_digest24_unmix",
|
||
"kwsg_10418_prefix8",
|
||
"kwsg_10418_prehash32",
|
||
"kwsg_10418_session_seed_from_time",
|
||
"kwsg_10418_sig3_hex",
|
||
"kwsg_10418_state_low24_from_source",
|
||
"load_kwsg_10418_sign_tables",
|
||
"load_kwsg_10418_tables",
|
||
]
|