"""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", ]