"""d0 weapon SDK crypto — pure-computation port of ``com.kuaishou.weapon.ks.d0``. Reverse-engineered from the APK Java fallback path (``d0.java``, ``l.java``, ``j1.java``, ``i0.java``, ``q.java`` under ``out/jadx/sources/com/kuaishou/weapon/ks/``). The native JNI path (``W.dc/dr/ar/ac``) is the accelerated equivalent; the Java fallback produces byte-identical output and is what we reproduce here. Layer map (verified against the jadx sources): q = ``android.util.Base64`` with flag NO_WRAP (standard alphabet) i0 = gzip (``a``=compress, ``b``=decompress) j1 = RC4 (KSA ``b(str)`` + PRGA ``a(data,key)``; symmetric) l = AES/CBC/PKCS5Padding (``a``=decrypt(key,iv,data), ``c``=encrypt(key,iv,data)) Key derivation (``d0.java:22`` / ``41`` / ``192``):: raw = base64_decode("a3NyaXNrY3RsYnVzaW5zc3Z4cHprd3NwYWlvcXBrc3M=") = b"ksriskctlbusinssvxpzkwspaioqpkss" key16 = first 16 chars -> "ksriskctlbusinss" (pad with '0' / truncate to 16) The same ``key16`` is used as BOTH the AES key and the AES IV — ``l.a``/``l.c`` are always called with ``(key16, key16, data)``. Transforms:: d0.encrypt(str) = base64( AES-CBC-enc(key16,key16, RC4(key16, gzip(plaintext))) ) d0.decrypt(str) = gunzip( RC4(key16, AES-CBC-dec(key16,key16, base64_decode(ct))) ) AES-CBC primitives are reused from :mod:`core.h5_kww_alg` (vendored pure-Python AES-128), so no new third-party crypto dependency is introduced. """ from __future__ import annotations import base64 import gzip from .h5_kww_alg import kwf_aes_cbc_decrypt, kwf_aes_cbc_encrypt _KEY_B64 = "a3NyaXNrY3RsYnVzaW5zc3Z4cHprd3NwYWlvcXBrc3M=" def _derive_key16() -> bytes: """Reproduce ``d0.java`` key normalization: pad-with-'0' or truncate to 16.""" raw = base64.b64decode(_KEY_B64) # q.a(bytes, 2) s = raw.decode("latin-1") # new String(bytes); bytes are ASCII if len(s) < 16: s = s + "0" * (16 - len(s)) elif len(s) > 16: s = s[:16] return s[:16].encode("latin-1") KEY16 = _derive_key16() # b"ksriskctlbusinss" def _rc4(key: bytes, data: bytes) -> bytes: """Textbook RC4 — port of ``j1.b`` (KSA) + ``j1.a`` (PRGA). Symmetric.""" s = list(range(256)) j = 0 klen = len(key) for i in range(256): j = (key[i % klen] + s[i] + j) & 0xFF s[i], s[j] = s[j], s[i] out = bytearray(len(data)) i = 0 j = 0 for n in range(len(data)): i = (i + 1) & 0xFF j = (s[i] + j) & 0xFF s[i], s[j] = s[j], s[i] out[n] = s[(s[i] + s[j]) & 0xFF] ^ data[n] return bytes(out) def decrypt(ciphertext_b64: str) -> str: """``d0.a(str)`` / ``d0.a(str,key)`` / static ``d0.b(str)`` — decrypt a d0 blob. Used by ``p1.java`` to decrypt the config-pull ``antispamSdkRsp`` field, and by ``d0.b(str)`` to decode hardcoded host/path strings. """ raw = base64.b64decode(ciphertext_b64) # q.a(bytes, 2) aes_out = kwf_aes_cbc_decrypt(raw, KEY16, KEY16) # l.a(key16,key16,_) rc4_out = _rc4(KEY16, aes_out) # j1.b(_,key16) plain = gzip.decompress(rc4_out) # i0.b(_) return plain.decode("utf-8") # new String(bytes) def encrypt(plaintext: str) -> str: """``d0.c(str)`` / ``d0.b(str,key)`` — encrypt a string into a d0 blob. Used to encrypt the config-pull request body. """ data = plaintext.encode("utf-8") # str.getBytes() gz = gzip.compress(data) # i0.a(_) rc4_out = _rc4(KEY16, gz) # j1.c(_,key16) aes_out = kwf_aes_cbc_encrypt(rc4_out, KEY16, KEY16) # l.c(key16,key16,_) return base64.b64encode(aes_out).decode("ascii") # q.c(_,2)