
微信 4.x 把本地聊天库锁进了 SQLCipher。想让程序读到聊天记录、图片、语音,绕不开一件事:先拿到那把解密的 key。
围绕"拿 key 再读库"这件事,社区有两条成熟的路子。一条是 wechat-use——macOS 上发消息 + 读库 + 实时监听的那套。另一条是开源的 wechat-decrypt——跨平台解密 + 批量导出 + 语音转文本 + 原生 MCP。
它俩经常被拿来比,但比错了方向。真正的区别不在"谁功能多",在怎么拿到那把 key,以及拿到之后能干嘛。这篇把两者拆到原理层。写完我在本机把 wechat-use 实际跑了一遍——结果订正了自己两个错判,后面会讲。
先把加密模型讲透
微信 4.x 的本地库是标准 SQLCipher 4 加密的 SQLite,放在:
macOS: ~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/<wxid>/db_storage

SQLCipher 的密钥模型有三个点必须先搞清楚,不然后面看不懂两个工具在抠什么:
- 一库一 key。 不是一把全局钥匙。
contact.db、message_*.db、sns.db… 每个库各有自己的 32 字节 raw key。我在本机看wechat-use存下来的~/.wx-rs/keys.json,里面正好是 19 个 per-DB 条目,每条{key_hex: 64 字符, salt_hex: 32 字符}。 - salt 藏在文件头。 每个加密 DB 文件的前 16 字节是随机 salt。这也是为什么加密的 SQLite 打开不是
SQLite format 3开头——那 16 字节被 salt 占了。wechat-decrypt的 C 代码里就有一句memcmp(header, "SQLite format 3", 15) == 0 → return -1:撞上明文库就跳过。 - raw key 只在内存里。 32 字节的 raw key 不落盘,微信运行时才把它派生出来、握在进程内存里,格式是
x'<64位 key hex><32位 salt hex>'(就是 SQLCipherPRAGMA key的原始形式,96 个十六进制字符)。
所以解一个库,要凑齐两样:raw key(从进程内存抠)+ salt(从 DB 文件头前 16 字节读)。wechat-decrypt 的 find_all_keys_macos.c 里 HEX_PATTERN_LEN 96、key_hex[65]、salt_hex[33] 这几个常量,对的就是这个模型。
"salt 在文件头"不是我瞎说,一次 hexdump 就能验。 我本机 contact.db 的前 16 字节:
$ xxd -l 16 contact/contact.db
00000000: 90f7 c71d 17b5 bfe6 24b1 76ac df40 df7f
明文 SQLite 该是 53 51 4c 69 74 65 …("SQLite format 3\000")开头;这里是一坨随机字节——这 16 字节就是 salt,加密把文件头顶掉了。wechat-decrypt 判断"是不是已明文库"用的就是这一句 memcmp(header, "SQLite format 3", 15)。
一号一整套库,不是一个文件。 微信按用途拆了近 20 个加密库,各有各的 key。我本机 ~/.wx-rs/keys.json 里 19 条,对应:
| 库 | 装什么 |
|---|---|
message/message_0.db、message_1.db | 聊天记录正文(按量分片) |
message/message_fts.db | 聊天全文搜索索引 |
message/media_0.db、message_resource.db | 消息里的媒体/资源 |
message/biz_message_0.db | 公众号/服务号消息 |
session/session.db | 会话列表(左边那一栏) |
contact/contact.db、contact_fts.db | 联系人 + 联系人搜索 |
sns/sns.db | 朋友圈 |
favorite/favorite.db、favorite_fts.db | 收藏 |
emoticon/emoticon.db / head_image/head_image.db | 表情 / 头像 |
hardlink/hardlink.db | 文件硬链接索引 |
bizchat、general、solitaire、weclaw | 企业号会话 / 通用配置 / 接龙 / 等 |
难点从来不是解密——SQLCipher 标准库给对 key 就开——难在从内存里把那把 key 抠出来。 两个工具真正拉开差距的,就是这一步。
原理核心:两条从内存抠 key 的路

要读微信进程的内存,macOS 有一道坎:微信带 hardened runtime 签名,系统默认不让别的进程 task_for_pid 去 attach 它——哪怕你是 root。两个工具过这道坎的方式,是最大的差异,也是我这篇改得最狼狈的地方(下面会讲我怎么错了两次)。
路线 A:wechat-decrypt —— replace-mode 重签 + sudo 扫内存
sudo codesign --force --deep --sign - /Applications/WeChat.app # ① replace-mode 重签成 ad-hoc
cc -O2 -o find_all_keys_macos find_all_keys_macos.c -framework Foundation
sudo ./find_all_keys_macos # ② root 扫内存
codesign --force --deep --sign - 是 replace-mode(替换式) 重签:把微信整个改成 ad-hoc 签名、连 hardened runtime 带微信自己的 entitlements 一起抹掉,然后靠 root 用 mach_vm_read 逐块(每块 2MB)扫内存,正则匹配那个 96-hex 的 key+salt 模式,命中写进 all_keys.json。salt 它另从 DB 文件头读。思路直白、逆向圈标准做法、全开源可审计。代价:要 root,而且替换式重签把微信原有权限一并抹了(极端情况会影响微信自己的 TCC)。
路线 B:wechat-use —— 读 key 不重签(登录瞬间调试接口)
这里是我一开始理解最拧巴的地方,拆两层说:
读 key(init)本身不重签、不 sudo。 它在微信登录那一瞬间用 macOS 公开调试接口抓一次解密材料,抓完立即 detach——不做 codesign --force --deep、不用 root。这跟路线 A 完全不同,路线 A 是先替换式重签再 root 硬扫。
要发消息才需要重签,而且是 merge-mode。 发送用的 slot_send 适配器需要微信 get-task-allow=true,这要跑一次 merge-mode(合并式) 重签——只加 get-task-allow 这一个 entitlement,保留微信自己原有的所有权限(SKILL 里明确警告不许用 replace-mode,因为那会把微信的 TCC 权限抹掉)。这是它比路线 A 克制的地方:同样动签名,它是外科式合并,不是一刀切替换。
我本机 wechat-use doctor 的输出:
✓ wechat_get_task_allow get-task-allow = true (LLDB 可 attach)
✓ wechat_codesigned signer: ad-hoc (本地重签,含 get-task-allow)
✓ wechat_dylib_fingerprint SHA-256=fc162db6… raw_key ✓ (keymap)
✓ wechat_update_guard ✓ disabled (Tencent 写不进 MacUpdate)
注意别被这个坑到(我就被坑了):这里显示"本地重签"是因为我这台机配过发送、做了那次 merge-mode 重签。它不是读 key 的必要条件——只读不发的话,init 的登录瞬间接口就够了。另外两个细节:wechat_dylib_fingerprint 说明它按微信 dylib 指纹选 keymap 定位 raw key(比硬扫固定 pattern 抗升级),wechat_update_guard: disabled 说明它把 MacUpdate 目录弄成写不进去,让重签不被升级冲掉。
一句话对比抠 key:wechat-decrypt 读 key = replace-mode 重签 + root 硬扫内存;wechat-use 读 key = 登录瞬间调试接口、不重签不 sudo(发消息才另做 merge-mode 重签)。 读 key 这一步,wechat-use 明显更轻、更克制;wechat-decrypt 更糙但全开源、跨平台一致。
一个值得抄的巧思:图片 key 不扫内存
wechat-decrypt 里有个细节挺聪明:图片的 key 不从进程内存拿,从磁盘的 kvcomm 缓存派生(find_image_key_macos.py)。微信把图片 .dat 的 key 落在了磁盘缓存里,解图片根本不用 attach 进程、不用 root。要自己做图片解密,这条路干净,可以直接抄。
解密本身:两边一模一样,手动也就几行
抠到 raw key、配上文件头的 salt,后面就没悬念了——都是标准 SQLCipher 4。拿到 key 之后,手动解一个库就这么几行(key 打码):
$ sqlcipher contact/contact.db
sqlite> PRAGMA key = "x'<64位 key hex…>'"; -- 内存抠来的 raw key
sqlite> PRAGMA cipher_compatibility = 4; -- 声明 SQLCipher 4
sqlite> .tables -- 能列出表 = 解开了
key 对,.tables 就把联系人表列出来;key 错,直接报 file is not a database。salt 不用手动喂——它在文件头前 16 字节,SQLCipher 自己会读。两个工具做的就是把这几行自动化,前面重签/attach/扫内存所有功夫,都只为拿到那个 x'…'。谁都没有独家解密算法,"谁解密更强"是伪命题,差异全在前面抠 key 那一步。
拿到库之后:能力就分叉了

抠 key 是入场券,拿到之后两个工具走向不同。这张表是我实测订正后的版本(打叉的地方就是第一版写错的):
| 维度 | wechat-use | wechat-decrypt |
|---|---|---|
| 发消息 | 后台零 UI 闪,带投递状态 | 不能发,只读 |
| 读库(会话/联系人/历史/图片) | 支持(本机实测 sessions 正常返回) | 全量解密成明文 SQLite |
| 语音转文本 | ✅ 也支持(v1.17:silk-decoder + whisper-cli + ggml-medium) | ✅ SILK→WAV→文本 |
| 抠 key 方式(macOS) | 读 key:登录瞬间调试接口,不重签、不 sudo(发消息才另做 merge-mode 重签) | replace-mode 重签 + sudo mach_vm 硬扫 |
| 升级适配 | dylib 指纹 keymap + probe-build 自适应 | 固定内存 pattern,等作者更新 |
| 平台 | 仅 macOS | Win / Mac / Linux |
| 企业微信 | 无 | 5.x 仅 Windows 实测 |
| 给 Claude 用(MCP) | 走 HTTP/gRPC,要自己包 | 原生 MCP,一行接入,20+ 工具 |
| 实时监听 | daemon RPC → SSE | Web UI SSE / CLI / MCP |
| 远程接入 | Cloudflare Tunnel 暴露 REST | 本地为主 |
| 成本 | 付费激活码 | 免费开源 |
这里就是实测的价值,而且我错了不止一次。 第一版凭读文档写:"wechat-use 不重签名、不转录语音"。跑起来看 doctor 显示"本地重签",我以为抓到错、改成"两边都重签才能读"——结果矫枉过正:那次重签是发消息配的,读 key 根本不重签。读全 SKILL + 源码才拼准:读 key 不重签,发消息才 merge-mode 重签;转录 v1.17 本来就有。 读文档错一次、看半个证据又错一次,把工具跑透、文档和源码读全,才敢下结论。
一句话读这张表:wechat-use 是"发 + 读 + 转录 + 实时"的全能 bot 平台(付费、闭源、只 macOS),wechat-decrypt 是"解密 + 导出 + 转录 + 原生 MCP"的开源数据工具(免费、跨平台、有企微,但不能发消息)。
各自的 workflow
wechat-use(发 + 读 + 实时)
① 订阅 TG 频道拿激活码
② wechat-use init # 登录瞬间调试接口抠 key,不重签、不 sudo(发消息才另做 merge-mode 重签);daemon 按需起
├─ 读: wechat-use sessions / history / contacts / search / image (YAML,低 token)
├─ 发: wechat-use send "文本" <名字或wxid> (后台零闪)
└─ 实时: wechat-use listen 或 wechat-bridge → HTTP /messages/stream?since=
③ 远程: wechat-use tunnel setup → Cloudflare Tunnel 暴露 REST
④ 升级: wechat-use probe-build → 适配新 build
wechat-decrypt(解密 + 导出 + MCP,macOS)
① python venv + pip install -r requirements.txt + brew install whisper-cpp
② 退出微信 → sudo codesign 重签 → cc 编译 → sudo ./find_all_keys_macos # root 扫内存抠 key
③ python decrypt_db.py # 全量解密 → 明文 SQLite(-i 增量)
├─ 导出: python export_all_chats.py -t # JSON/CSV/HTML + 语音转录 + 增量/日期窗口
├─ 实时: python main.py # Web UI localhost:5678 (SSE)
└─ 给 Claude: claude mcp add wechat -- python mcp_server.py # 20+ 查询工具
mcp_server.py 一行接进 Claude,get_chat_history / search_messages / decode_voice 直接查。它解析朋友圈等 XML 时还加了 XXE 防护(拒绝 DOCTYPE/ENTITY + 大小上限),这点比很多同类严谨。
组合起来(真实场景:agent 接微信)
发消息 / 实时响应 / 群@ bot ──────────→ wechat-use (成熟,已在用)
批量拉历史 / 企微 / 给 Claude 原生查 ──→ wechat-decrypt MCP (补 MCP + 跨平台)
两者不冲突,但别同时跑——都要 attach 微信、都动它的签名和 DB,一台机器上同时上两套,签名和 attach 会打架。
两个容易被忽略的点
语音怎么变成文本,两边一样。 微信语音不是 mp3,是 SILK(Skype 开源的窄带语音 codec)。所以转文本是三段流水线:SILK → 解成 WAV → 丢给 whisper 转文字。wechat-decrypt 内置 silk decoder + whisper.cpp/OpenAI;wechat-use v1.17 也把 silk-decoder + whisper-cli + ggml-medium 打包进去了,读群历史时语音自动转。这条链跟做 TTS 的欢迎语其实是反的——那边文字→语音,这边语音→文字,但都靠 whisper。
个人微信和企业微信不是一把锁。 个人微信 4.x 用 SQLCipher 4(前面讲的那套:文件头 salt + 内存 raw key)。企业微信 5.x 是另一套——wxSQLite3 的 AES-128,加密参数、key 位置都不同,所以 wechat-decrypt 单独写了 find_wxwork_keys.py,而且只在 Windows 实测过。wechat-use 干脆不碰企微。想解企微,目前只有 wechat-decrypt + Windows 这一条路。
风险与边界(这是解密工具,得说清楚)
- 都可能改微信签名,但方式和时机不同。
wechat-decrypt为读 key 就做 replace-mode 重签(把微信原有权限一并抹掉)+ root;wechat-use读 key 不重签,只有发消息才 merge-mode 加get-task-allow(保留原权限),并主动关掉自动更新来保住它。不管哪种,ad-hoc 重签都让微信从官方签名变成本地签名,理论上有被检测、Gatekeeper 提示的可能。动手前知道自己在改什么。 - 只解自己的号、自己的数据。 这类工具的正当用途是导出/备份自己的聊天,不是去读别人的。
- key 和聊天内容不要外泄。 raw key 一旦泄露,谁都能解你的库。本文里所有真实 key、聊天内容全部脱敏——写这类文章、贴日志时都该这么做。
- 需要开发者模式 / attach 权限。
wechat-use要 macOS Developer Mode + get-task-allow;wechat-decrypt要 root。都是"能读任意进程内存"级别的权限,给之前想清楚。
那么,他的实现比我们的好吗
分开看:
- 抠 key 的手法:
wechat-use更轻更克制。 读 key 它压根不重签(登录瞬间调试接口、不 sudo);要发消息才 merge-mode 重签(只加 get-task-allow、保留微信原权限)。wechat-decrypt读 key 就得 replace-mode 重签 + root 硬扫,更糙、还会连微信原有权限一起抹,但全开源、可审计、跨平台一套。 - 解密本身:一样。 都是标准 SQLCipher 4,没人有黑科技。
- 能力覆盖:各有各的。
wechat-use独占"发消息";wechat-decrypt独占"原生 MCP + 跨平台 + 企微"。语音转录两边都有(这点我一开始搞错了)。
准确的说法:这不是"谁实现更好",是两条路线。 抠 key 的优雅度和"能发消息"上 wechat-use 赢;开源、免费、跨平台、原生 MCP 上 wechat-decrypt 赢。
选型结论
- 要发消息,离不开
wechat-use。wechat-decrypt压根不能发,替代不了。 wechat-decrypt值得引入的,是它补的两块: 免费的原生 MCP(让 Claude 一行接入、直接查聊天历史)、跨平台 + 企微。语音转录不再是它的独有卖点——两边都有。- 落地:
wechat-use继续当发送 + 实时 + 读库主力;需要"Claude 原生查微信数据"或"读企微/在 Linux 跑"时,加装wechat-decrypt的 MCP。别在同一台机器同时开两套。
技术上能从对方抄两样:图片 key 从磁盘 kvcomm 缓存派生(不用 attach 进程),x'key+salt' 96-hex 的内存格式配 keymap 定位(比硬扫 pattern 抗升级)。哪天要自研或加固,这两个 trick 现成。
最后留一句方法论:这篇最有价值的不是那张对比表,是我错了两次的过程——读文档写错一次,看半个证据(doctor 的重签)又矫枉过正一次,把工具跑透、SKILL 和源码读全,才敢下结论。写对比最危险的不是没读,是读了一半就下笔——而误导的源头,恰恰是工具自己文档里那句没分清"读 key"和"发消息"的 no re-signing。

微信
支付宝
评论
评论发布后会立即公开,如触发规则可能被审核下架。