mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-02-19 22:30:49 +08:00
feat(sns): 增强朋友圈时间线/媒体获取与实时同步
- 新增 /api/sns/users:按发圈数统计联系人(支持 keyword/limit) - 新增 /api/sns/realtime/sync_latest:WCDB 实时增量同步到解密库(append-only),并持久化 sync state - 朋友圈媒体优先走“远程下载+解密”:图片支持 wcdb_decrypt_sns_image,视频/实况支持 ISAAC64(WeFlow 逻辑) - 增加 WeFlow WASM keystream(Node) 优先 + Python ISAAC64 fallback,提升兼容性 - wcdb_api.dll 支持多路径自动发现/环境变量覆盖,并在状态信息中回传实际使用路径
This commit is contained in:
169
src/wechat_decrypt_tool/isaac64.py
Normal file
169
src/wechat_decrypt_tool/isaac64.py
Normal file
@@ -0,0 +1,169 @@
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user