ksjsb/core/sig3.py
2026-07-30 20:25:56 +08:00

358 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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