from __future__ import annotations """ISAAC-64 PRNG (WeFlow compatible). WeChat SNS live photo/video decryption uses a keystream generated by ISAAC-64 and XORs the first 128KB of the mp4 file. WeFlow's implementation reverses the generated byte array, so we mirror that behavior for compatibility. """ from typing import Any _MASK_64 = 0xFFFFFFFFFFFFFFFF def _u64(v: int) -> int: return int(v) & _MASK_64 class Isaac64: def __init__(self, seed: Any): seed_text = str(seed).strip() if not seed_text: seed_val = 0 else: try: # WeFlow seeds with BigInt(seed), where seed is usually a decimal string. seed_val = int(seed_text, 0) except Exception: seed_val = 0 self.mm = [_u64(0) for _ in range(256)] self.aa = _u64(0) self.bb = _u64(0) self.cc = _u64(0) self.randrsl = [_u64(0) for _ in range(256)] self.randrsl[0] = _u64(seed_val) self.randcnt = 0 self._init(True) def _init(self, flag: bool) -> None: a = b = c = d = e = f = g = h = _u64(0x9E3779B97F4A7C15) def mix() -> tuple[int, int, int, int, int, int, int, int]: nonlocal a, b, c, d, e, f, g, h a = _u64(a - e) f = _u64(f ^ (h >> 9)) h = _u64(h + a) b = _u64(b - f) g = _u64(g ^ _u64(a << 9)) a = _u64(a + b) c = _u64(c - g) h = _u64(h ^ (b >> 23)) b = _u64(b + c) d = _u64(d - h) a = _u64(a ^ _u64(c << 15)) c = _u64(c + d) e = _u64(e - a) b = _u64(b ^ (d >> 14)) d = _u64(d + e) f = _u64(f - b) c = _u64(c ^ _u64(e << 20)) e = _u64(e + f) g = _u64(g - c) d = _u64(d ^ (f >> 17)) f = _u64(f + g) h = _u64(h - d) e = _u64(e ^ _u64(g << 14)) g = _u64(g + h) return a, b, c, d, e, f, g, h for _ in range(4): mix() for i in range(0, 256, 8): if flag: a = _u64(a + self.randrsl[i]) b = _u64(b + self.randrsl[i + 1]) c = _u64(c + self.randrsl[i + 2]) d = _u64(d + self.randrsl[i + 3]) e = _u64(e + self.randrsl[i + 4]) f = _u64(f + self.randrsl[i + 5]) g = _u64(g + self.randrsl[i + 6]) h = _u64(h + self.randrsl[i + 7]) mix() self.mm[i] = a self.mm[i + 1] = b self.mm[i + 2] = c self.mm[i + 3] = d self.mm[i + 4] = e self.mm[i + 5] = f self.mm[i + 6] = g self.mm[i + 7] = h if flag: for i in range(0, 256, 8): a = _u64(a + self.mm[i]) b = _u64(b + self.mm[i + 1]) c = _u64(c + self.mm[i + 2]) d = _u64(d + self.mm[i + 3]) e = _u64(e + self.mm[i + 4]) f = _u64(f + self.mm[i + 5]) g = _u64(g + self.mm[i + 6]) h = _u64(h + self.mm[i + 7]) mix() self.mm[i] = a self.mm[i + 1] = b self.mm[i + 2] = c self.mm[i + 3] = d self.mm[i + 4] = e self.mm[i + 5] = f self.mm[i + 6] = g self.mm[i + 7] = h self._isaac64() self.randcnt = 256 def _isaac64(self) -> None: self.cc = _u64(self.cc + 1) self.bb = _u64(self.bb + self.cc) for i in range(256): x = self.mm[i] if (i & 3) == 0: # aa ^= ~(aa << 21) self.aa = _u64(self.aa ^ (_u64(self.aa << 21) ^ _MASK_64)) elif (i & 3) == 1: self.aa = _u64(self.aa ^ (self.aa >> 5)) elif (i & 3) == 2: self.aa = _u64(self.aa ^ _u64(self.aa << 12)) else: self.aa = _u64(self.aa ^ (self.aa >> 33)) self.aa = _u64(self.mm[(i + 128) & 255] + self.aa) y = _u64(self.mm[(x >> 3) & 255] + self.aa + self.bb) self.mm[i] = y self.bb = _u64(self.mm[(y >> 11) & 255] + x) self.randrsl[i] = self.bb def get_next(self) -> int: if self.randcnt == 0: self._isaac64() self.randcnt = 256 idx = 256 - self.randcnt self.randcnt -= 1 return _u64(self.randrsl[idx]) def generate_keystream(self, size: int) -> bytes: """Generate a keystream of `size` bytes (must be multiple of 8).""" if size <= 0: return b"" if size % 8 != 0: raise ValueError("ISAAC64 keystream size must be multiple of 8 bytes.") out = bytearray() count = size // 8 for _ in range(count): out.extend(int(self.get_next()).to_bytes(8, "little", signed=False)) # WeFlow reverses the entire byte array (Uint8Array.reverse()). out.reverse() return bytes(out)