Files
WeChatDataAnalysis/tools/debug_decrypt_file.py
2977094657 eaec54a517 feat(chat): 增强聊天媒体能力(表情下载/资源定位/会话预览)
新增 /api/chat/media/emoji/download,支持将表情资源下载到本地 resource

消息列表补充 emojiRemoteUrl,本地存在资源时优先返回本地 emojiUrl

open_folder 行为增强:更智能定位 emoji/资源目录,并改进 Windows Explorer 打开方式

会话列表预览改为使用 _load_latest_message_previews,提升 last message 准确性

工具脚本移除对 WxDatDecrypt 的依赖,媒体密钥提取逻辑内置到 media_key_finder

wheel 打包包含 VoipEngine.dll(Windows 解码依赖随包分发)
2025-12-18 21:19:02 +08:00

125 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""直接测试文件解密逻辑"""
import sys
sys.path.insert(0, "src")
import json
import struct
from pathlib import Path
# 测试参数
ACCOUNT_DIR = Path(r"d:\abc\PycharmProjects\WeChatDataAnalysis\output\databases\wxid_v4mbduwqtzpt22")
TEST_FILE = Path(r"D:\abc\wechatMSG\xwechat_files\wxid_v4mbduwqtzpt22_1e7a\msg\attach\0d6a4127daada32c5e407ae7201e785a\2025-12\Img\0923ad357c321cf286b794f8e5a66333.dat")
WXID_DIR = Path(r"D:\abc\wechatMSG\xwechat_files\wxid_v4mbduwqtzpt22_1e7a")
# ========== 1. 读取密钥 ==========
print("[1] 读取密钥文件")
keys_file = ACCOUNT_DIR / "_media_keys.json"
if keys_file.exists():
with open(keys_file, "r", encoding="utf-8") as f:
keys = json.load(f)
print(f" keys = {keys}")
xor_key = keys.get("xor")
aes_str = str(keys.get("aes") or "").strip()
aes_key16 = aes_str.encode("ascii", errors="ignore")[:16] if aes_str else b""
print(f" xor_key = {xor_key}")
print(f" aes_key16 = {aes_key16}")
else:
print(" [ERROR] 密钥文件不存在")
sys.exit(1)
# ========== 2. 读取测试文件 ==========
print(f"\n[2] 读取测试文件: {TEST_FILE}")
with open(TEST_FILE, "rb") as f:
data = f.read()
print(f" 文件大小: {len(data)} bytes")
print(f" 前 16 字节: {data[:16].hex()}")
# ========== 3. 检测版本 ==========
print("\n[3] 检测文件版本")
sig = data[:6]
if sig == b"\x07\x08V1\x08\x07":
version = 1
print(" 版本: V1")
elif sig == b"\x07\x08V2\x08\x07":
version = 2
print(" 版本: V2")
else:
version = 0
print(" 版本: V0 (纯 XOR)")
# ========== 4. 尝试解密 ==========
print("\n[4] 尝试解密")
from Crypto.Cipher import AES
from Crypto.Util import Padding
def decrypt_v4(data: bytes, xor_key: int, aes_key: bytes) -> bytes:
"""使用 api.py 相同的解密逻辑"""
header, rest = data[:0xF], data[0xF:]
print(f" 头部 (15 bytes): {header.hex()}")
signature, aes_size, xor_size = struct.unpack("<6sLLx", header)
print(f" signature: {signature}")
print(f" aes_size: {aes_size}")
print(f" xor_size: {xor_size}")
# 对齐到 AES 块大小
aes_size_aligned = aes_size + (AES.block_size - aes_size % AES.block_size) if aes_size % AES.block_size != 0 else aes_size
print(f" aes_size_aligned: {aes_size_aligned}")
aes_data = rest[:aes_size_aligned]
print(f" aes_data 长度: {len(aes_data)}")
print(f" aes_data 前 16 字节: {aes_data[:16].hex()}")
cipher = AES.new(aes_key[:16], AES.MODE_ECB)
decrypted_aes_raw = cipher.decrypt(aes_data)
print(f" 解密后 (带 padding) 前 16 字节: {decrypted_aes_raw[:16].hex()}")
try:
decrypted_data = Padding.unpad(decrypted_aes_raw, AES.block_size)
print(f" 去 padding 后长度: {len(decrypted_data)}")
except Exception as e:
print(f" [WARN] unpad 失败: {e}, 使用原始数据")
decrypted_data = decrypted_aes_raw
if xor_size > 0:
raw_data = rest[aes_size_aligned:-xor_size]
xor_data = rest[-xor_size:]
xored_data = bytes(b ^ xor_key for b in xor_data)
print(f" raw_data 长度: {len(raw_data)}")
print(f" xor_data 长度: {len(xor_data)}")
else:
raw_data = rest[aes_size_aligned:]
xored_data = b""
print(f" raw_data 长度: {len(raw_data)}")
result = decrypted_data + raw_data + xored_data
print(f" 最终结果长度: {len(result)}")
print(f" 结果前 16 字节: {result[:16].hex()}")
# 检查是否是有效图片
if result[:3] == b"\xff\xd8\xff":
print(" [OK] 解密成功! 是 JPEG 图片")
elif result[:8] == b"\x89PNG\r\n\x1a\n":
print(" [OK] 解密成功! 是 PNG 图片")
else:
print(" [WARN] 解密后不是有效图片头")
return result
if version == 2 and xor_key is not None and aes_key16:
print("\n[4.1] 使用本地 decrypt_v4 函数:")
decrypted = decrypt_v4(data, xor_key, aes_key16)
# 保存解密后的文件
output_file = Path("test_decrypted_manual.jpg")
with open(output_file, "wb") as f:
f.write(decrypted)
print(f" 已保存: {output_file} ({len(decrypted)} bytes)")
else:
print(" [ERROR] 无法解密: 缺少必要参数")
print("\n[Done]")