wechat-decrypt · diff
git:20260610.5177d1c to git:20260901.823363c
40 added, 148 removed. Audit A to A.
---
name: wechat-decrypt
- description: WeChat 4.x chat decrypt & query (macOS + Windows, both verified). Use when user asks about their WeChat messages, chats, contacts, or needs to re-extract the encryption key after WeChat update.
- allowed-tools: Bash
+ description: Read, search, summarize, export, and transcribe a user's own local WeChat 4.x history on macOS or Windows; diagnose setup and re-extract the device key after a WeChat update. Use for WeChat messages, chats, contacts, and local archives. Do not use for remote accounts or devices the user is not authorized to access.
---
- # WeChat 4.x 聊天记录解密与查询(macOS + Windows)
-
- ## 首次加载自检(Agent 必须执行)
-
- ```bash
- python3 -c "import platform; print(platform.system())"
- claude mcp list 2>&1 | grep -q "wechat.*Connected" && echo "MCP_OK" || echo "MCP_MISSING"
- test -f ~/.claude/skills/wechat-decrypt/key.txt && echo "KEY_OK" || echo "KEY_MISSING"
- ```
-
- | 状态 | 行动 |
- |------|------|
- | `MCP_MISSING` | macOS `bash setup.sh` / Windows `powershell -File setup.ps1`,告知「重启 Claude Code」 |
- | `KEY_MISSING` | 执行「密钥提取」对应平台流程(Windows 提 key 后还需 `python scripts\windows\decrypt_all.py` 解密全库到明文) |
- | 全部 OK | 直接使用 MCP 工具 |
-
- > **Windows 自检**(PowerShell,对齐上面 macOS 三项):
- > ```powershell
- > python -c "import frida, Crypto; print('DEPS_OK')" # 缺 → pip install frida pycryptodome
- > Test-Path "$env:USERPROFILE\.claude\skills\wechat-decrypt\key_windows.txt" # 缺 → extract_raw_key.py 提 key
- > Test-Path "$env:USERPROFILE\.claude\skills\wechat-decrypt\decrypted" # 缺 → decrypt_all.py 解密全库
- > ```
- > 缺哪项跑对应步骤:deps→`setup.ps1`、key→`extract_raw_key.py`、明文库→`decrypt_all.py`。
+ # WeChat local history
- 不要跳过自检。
+ Operate only on local WeChat data the user is authorized to access. Keep keys, decrypted databases, voice caches, and exports local. Never print a raw key or include one in chat, logs, commands, or reports.
- ## Agent 触发规则
+ ## Resolve the runtime
- | 用户意图 | 行动 |
- |----------|------|
- | "看我微信" "最近聊天" "谁找我" "有什么消息" | MCP: `wechat_recent_messages` / `wechat_chat_summary` |
- | "搜XX的聊天" "和XX提到" | MCP: `wechat_search_messages` |
- | "列出会话" "有哪些群" | MCP: `wechat_list_chats` |
- | "读和XX的聊天" | MCP: `wechat_read_chat` |
- | "导出和XX的聊天" "导出XX到桌面" | 先做①模型检查(见下),再 `python3 scripts/common/export_chat.py <contact> [--start/--end/--year] [-o path]`(不传日期=全部历史;模型在则默认转写,`--no-transcribe` 可关;语音转写结果持久缓存 voice_cache.json,二次导出秒回) |
- | "转写XX的语音" "语音转文字" "把语音导成文字" | `python3 scripts/common/export_chat.py <contact> --transcribe`,或单独 `python3 scripts/common/transcribe_db.py <contact> -o map.json` |
- | "微信更新了" "重新破解" "密钥失效" | 执行下方「密钥提取」对应平台流程 |
+ Use the directory containing this `SKILL.md` as `SKILL_DIR`; never assume a fixed installation path.
- ### ① 导出前置——语音转写模型检查(默认执行)
+ - macOS: prefer `$SKILL_DIR/.venv/bin/python`, otherwise `python3`.
+ - Windows: prefer `$SKILL_DIR\.venv\Scripts\python.exe`, otherwise `python`.
- 导出/转写任何可能含语音的对话前,先查模型是否就位:
+ Before reading chat data, run the read-only diagnostic:
```bash
- test -d ~/.cache/huggingface/hub/models--mlx-community--whisper-large-v3-mlx && echo MODEL_OK || echo MODEL_MISSING
+ "$SKILL_DIR/.venv/bin/python" "$SKILL_DIR/scripts/common/doctor.py" --json
```
- | 结果 | 行动 |
- |------|------|
- | `MODEL_OK` | 导出**默认开启语音转写**(脚本检测到模型即自动转,无需 `--transcribe`;用户明确不要才加 `--no-transcribe`) |
- | `MODEL_MISSING` | **先问用户是否安装**(whisper-large-v3 ~3GB,纯离线一次性)。装→加 `--transcribe`(首次自动下载并转写);不装→普通导出,语音保留 `[Audio]` |
-
- 脚本侧已内置同逻辑(模型在→默认转,不在→留 `[Audio]` 并提示)。但 `MODEL_MISSING` 时 Agent 必须主动问,别擅自下 3GB。
-
- ## MCP 工具(注册名 `wechat`)
-
- | 工具 | 参数 | 用途 |
- |------|------|------|
- | `wechat_list_chats` | 无 | 所有会话(群名+备注已解析) |
- | `wechat_read_chat` | contact, limit(50), days(7) | 特定对话 |
- | `wechat_search_messages` | keyword, days(30), limit(50) | 全文搜索 |
- | `wechat_recent_messages` | days(3), limit(100) | 最近动态 |
- | `wechat_chat_summary` | days(3) | 结构化摘要 + 待办 |
-
- `[我]` = 用户发的,`[对方]` = 联系人发的。
+ On Windows, use the equivalent venv Python path. Interpret `fail` as blocking and `warn` as optional/degraded:
- ## 语音转写(全自动)
+ - `key` or `database` failure: read the matching platform reference below.
+ - `mcp` warning: use the CLI fallback now; run platform setup only when MCP registration is needed.
+ - `voice-model` warning: ordinary export still works. Do not download the model without user approval.
- 语音消息(`local_type=34`)的音频以 SILK v3 存在 `media_0.db` 的 `VoiceInfo.voice_data`,按 `svr_id == server_id` 对齐。链路:VoiceInfo 直取 → `pilk` 解码 SILK→wav → whisper large-v3 转写 → 按 server_id 回填。全自动、批量、纯离线,无需 BlackHole/播放/重启。**默认行为:模型已缓存即自动转写;`--transcribe` 强制(含首次下载 ~3GB 模型,缓存 `~/.cache/huggingface/`);`--no-transcribe` 关闭。** **后端按平台自动切**:macOS = `mlx-whisper`(Apple Silicon),Windows = `faster-whisper`(CPU int8,按需下载);旧 BlackHole+Swift 方案在 langlobal 开发仓。
+ ## Route the request
- ## 命令行 entry(逻辑核心;MCP 是可选薄门面)
+ | Intent | Preferred action |
+ |---|---|
+ | List chats or groups | `wechat_list_chats` |
+ | Read one chat | `wechat_read_chat` |
+ | Search all chats | `wechat_search_messages` |
+ | Review recent activity | `wechat_recent_messages` |
+ | Summarize recent chats and action items | `wechat_chat_summary` |
+ | Review pats, recalls, group changes, payments, or calls | `wechat_system_events`; filter with a stable code or Chinese label |
+ | Statistics, media, or a received document | CLI `stats`, `media`, or `openfile` |
+ | Export history or transcribe voice | Read [references/export-transcription.md](references/export-transcription.md) |
+ | macOS setup, key failure, or WeChat update | Read [references/macos.md](references/macos.md) |
+ | Windows setup, key failure, or WeChat update | Read [references/windows.md](references/windows.md) |
- 逻辑核心在 `scripts/common/query.py`(命令行),`server.py` 的 MCP 工具只是转发到它。**agent 调用推荐加 `--json` 拿结构化输出**(解析可靠,不受文字排版影响);人调试时不加、看文字。
+ MCP is a thin optional facade. If it is unavailable, use the same logic through `scripts/common/query.py` and request JSON:
```bash
- SKILL_DIR=~/.claude/skills/wechat-decrypt
- python3 "$SKILL_DIR/scripts/common/query.py" list
- python3 "$SKILL_DIR/scripts/common/query.py" recent -d 3 --json # agent 用 --json
- python3 "$SKILL_DIR/scripts/common/query.py" search 关键词 --json
- # 子命令: list / read <contact> / search <kw> / recent / summary / stats / media / openfile
+ "$PYTHON" "$SKILL_DIR/scripts/common/query.py" list --json
+ "$PYTHON" "$SKILL_DIR/scripts/common/query.py" read CONTACT -d 7 -n 50 --json
+ "$PYTHON" "$SKILL_DIR/scripts/common/query.py" search KEYWORD -d 30 -n 50 --json
+ "$PYTHON" "$SKILL_DIR/scripts/common/query.py" recent -d 3 -n 100 --json
+ "$PYTHON" "$SKILL_DIR/scripts/common/query.py" summary -d 3 --json
+ "$PYTHON" "$SKILL_DIR/scripts/common/query.py" events -e pat -d 30 -n 100 --json
```
- ## 密钥体系(两端通用)
-
- - **Raw Key**: 64 hex,**每设备独立**(微信 4.0:同账号 macOS 与 Windows 的 raw key 不同,不可互用);微信不更新就持续有效
- - **派生公式**: `PBKDF2-HMAC-SHA512(raw_key, db_file_salt, 256000) → 32 bytes`
- - **SQLCipher 参数**: `PRAGMA key = "x'<derived>'"; PRAGMA kdf_iter = 1; PRAGMA cipher_compatibility = 4; PRAGMA cipher_page_size = 4096;`
-
- ## 数据库路径
-
- | 平台 | 路径 |
- |------|------|
- | macOS | `~/Library/Containers/com.tencent.xinWeChat/.../xwechat_files/{wxid}/db_storage/` |
- | Windows | `~/Documents/xwechat_files/{wxid}/db_storage/` |
-
- ---
-
- # 密钥提取
-
- ## macOS
-
- ### Agent 执行流程
-
- 1. 检查重签名:`codesign -dv /Applications/WeChat.app 2>&1 | grep -q "Signature=adhoc" || echo "NEED_RESIGN"`
- - `NEED_RESIGN` → 告知用户执行 `sudo codesign --force --deep --sign - /Applications/WeChat.app`
- 2. 运行 `bash ~/.claude/skills/wechat-decrypt/scripts/macos/extract_key.sh`
- 3. 脚本会 spawn 微信 → **告知用户扫码登录** → 登录后 key 自动写入 `key.txt`
- 4. 验证:`test -f ~/.claude/skills/wechat-decrypt/key.txt && echo "OK"`
- 5. **提取后必须告知用户恢复签名**:adhoc 重签名是单向破坏性操作,会覆盖腾讯原始签名,导致微信截图/数据访问反复弹权限框。codesign 无法还原第三方签名,只能重装微信恢复。聊天记录在独立容器目录(`~/Library/Containers/com.tencent.xinWeChat/`),重装 `/Applications/WeChat.app` 不受影响。密钥提取后日常查消息只读 DB、不再需要重签名。
-
- ### 关键认知
-
- - 重签名只在「提取密钥」这一瞬间需要,提取完即应重装微信恢复签名。
- - 反复弹权限框 = 微信签名是 adhoc 状态的症状,不是 Claude Code/MCP 引起的。
- - 密钥每设备独立(同账号各端 key 不同,不可互用),微信不更新就一直有效,无需反复提取。
-
- ### 原理
-
- 微信 4.x macOS 版不调 `sqlite3_key`(苹果系统 SQLite 的该函数为空壳),而是通过 WCDB 统一加密层调用 `CCKeyDerivationPBKDF`(CommonCrypto)。Frida 使用 `Module.findGlobalExportByName`(Frida 17.x+ API)hook 该函数,在 `passwordLen=32, rounds>1000, dkLen=32` 时捕获密码参数(raw key)。
-
- ## Windows(已攻克:提 key→解密→读取,能力对标 macOS)
-
- **现状(2026-06-06 UTM 实测跑通)**:提 key→解密全库→MCP/导出/语音转写,与 macOS 同能力。message_0 解出 99 会话/1384 消息。全流程见 `docs/2026-06-06-windows-raw-key-journey.md`。
-
- ### Agent 执行流程
- 1. 登录微信
- 2. **提 key**:`python scripts\windows\extract_raw_key.py` → 按提示**桌面手动重启微信**(必须;schtasks/SSH 起的是空壳、不读 db)→ 脚本自动 race-attach + hook sha512 抓 raw key + 验证,写入 `key_windows.txt`
- 3. **解密全库**:`python scripts\windows\decrypt_all.py` → 用 raw key 把 db_storage 所有库解密到 `decrypted\{wxid}_<device>\db_storage\`(明文)
- 4. 重启 Claude Code → **MCP 工具、导出、语音转写与 macOS 完全一致**(server/export/transcribe 走 `DB_BACKEND="sqlite3"` 读明文库;语音用 faster-whisper)
-
- 单库验证/调试用 `scripts\windows\decrypt_read.py <raw_key>`(只解 message_0 验证)。
-
- ### 提 key 原理(sha512 入口 hook + HMAC ipad)
- **核心**:raw key/K1 在内存被三重保护(AES-NI 轮密钥/用后清除/secure mem,内存 brute 全失败),但 SQLCipher 派生 key 走 **PBKDF2-HMAC-SHA512**,HMAC 构造 ipad block 那一刻是 key 的**明文** `key XOR 0x36`(后 96 字节恒 0x36)——绕过所有保护直取。
- `extract_raw_key.py` 自包含:**自动定位** sha512 入口(搜 K 常量 `0x428a2f98d728ae22`→xref→反汇编,不硬编码地址,换微信版本也行)+ race-attach(赶在开 db 前)+ hook 入口(rdx=input block)+ 检测 ipad + 自动验证(`PBKDF2→AES page1` 出头=raw key 通用 / 直接 `AES` 出头=K1 单库)。启动 `PBKDF2(raw,salt)` 暴露 raw key;运行时 page-MAC 只暴露 mac_key(无用)→ 必须 race 赶**启动**。
- **frida 在 ARM 模拟(xtajit 翻译 x64)下能 hook 微信内部 x64 函数**(1127万次验证);spawn 不兼容用 race-attach。
- - 数据路径:`C:\Users\<用户>\Documents\xwechat_files\{wxid}_<device>\db_storage\`。`wechat-dump-rs` 被 DMCA 封,本方法已替代。
- - 开发期全套攻关脚本(定位/brute/失败记录)在 langlobal 开发仓,运行只需 `extract_raw_key.py`+`decrypt_all.py`。
-
- ---
-
- ## 分发与安装
-
- 拷贝整个 skill 目录。接收方执行:
-
- **macOS:** `bash setup.sh`
- **Windows:** `powershell -File setup.ps1`
-
- `key.txt`、`contacts.json`、`all_keys.json`、`voice_cache.json` 是个人数据,分发前删除(已 `.gitignore`)。
-
- ## 文件清单
+ `[我]` means the account owner; `[对方]` means the peer. If contact matching is ambiguous, show the candidates and ask the user to choose; do not guess. Keep time windows and limits proportional to the request, and do not dump full history unless explicitly requested.
- ```
- ~/.claude/skills/wechat-decrypt/ # 运行单元;开发全套(tests/docs/攻关脚本/legacy)在 langlobal 开发仓
- ├── server.py config.py crypto.py db.py contacts.py message.py ← MCP 运行核心(6),db.py 双后端
- ├── scripts/
- │ ├── common/ ← 跨平台:export_chat(导出+转写) transcribe_db voice_decode sqlcipher_decrypt read_doc export_media verify_key
- │ ├── macos/ ← extract_key.sh + hook_pbkdf.js (CCKeyDerivationPBKDF)
- │ └── windows/ ← extract_raw_key.py(自包含提key) decrypt_all.py(解密全库→明文) decrypt_read.py(单库验证)
- ├── setup.sh / setup.ps1 ← 平台安装
- ├── SKILL.md README.md LICENSE
- ├── key.txt key_windows.txt ← .gitignore
- └── contacts.json ← .gitignore
- ```
+ ## Operational invariants
- 依赖:**macOS** 全局 python(mcp/frida/cryptography/pilk/mlx-whisper/zstd);**Windows**(mcp/frida/pycryptodome/pilk/faster-whisper/zstandard)。语音模型缓存 `~/.cache/huggingface/`。
+ - Raw keys are 64 hexadecimal characters and are device-specific. macOS and Windows keys for the same account are not interchangeable.
+ - macOS queries encrypted databases in place through SQLCipher, always read-only.
+ - Windows first creates a local plaintext mirror under `decrypted/`, then queries it read-only. Treat that mirror as sensitive.
+ - WeChat updates may invalidate extraction assumptions or require a new key. Run `doctor.py` before repeating extraction.
+ - Voice transcription is offline after the model is cached. A first large-v3 download is about 3 GB and always requires user approval.