Extract SQLCipher database keys from WeChat for Mac 4.x (tested 4.1.13) so its local chat databases can be decrypted and queried. Use when a user on macOS wants to read/decrypt/export their own local WeChat data and existing tools (e.g. wechat-cli, wechat-decrypt) fail with 0 keys / "cannot decrypt" because they only support WeChat <= 4.1.8. Runtime: macOS (Apple Silicon or Intel), Python 3.10+, frida < 17, pycryptodome; requires sudo and a running, logged-in WeChat that has been ad-hoc re-si...
2 stars
0 votes
0 copies
0 views
Added September 23, 2026
ai-agentspythonrustbashsqldatabasesecurity
Works with
terminal
cli
Security analysis
A96/100
mediumInstalls packages at runtime which could introduce malicious dependencies
Installs into .claude/skills of the current project.
Are you the author of wechat-key-macos?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/3351666087-wechat-key-macos)
---
name: wechat-key-macos
version: 1.0.0
description: >-
Extract SQLCipher database keys from WeChat for Mac 4.x (tested 4.1.13) so its
local chat databases can be decrypted and queried. Use when a user on macOS
wants to read/decrypt/export their own local WeChat data and existing tools
(e.g. wechat-cli, wechat-decrypt) fail with 0 keys / "cannot decrypt" because
they only support WeChat <= 4.1.8. Runtime: macOS (Apple Silicon or Intel),
Python 3.10+, frida < 17, pycryptodome; requires sudo and a running, logged-in
WeChat that has been ad-hoc re-signed with get-task-allow.
license: Apache-2.0
compatibility:
os: macOS 13+ (tested 26.6.2)
arch: [arm64, x86_64]
wechat_mac: "4.0 – 4.1.13 (tested 4.1.13)"
python: ">=3.10 (tested 3.14)"
pins:
frida: "<17 (tested 16.7.19)" # frida 17 removed the built-in JS bridges this relies on
pycryptodome: ">=3.19"
---
# wechat-key-macos
Extract per-database SQLCipher keys from a **running WeChat for Mac 4.x** and use
them to decrypt/query the local databases. This is agent-agnostic: any coding
agent (or a human) can follow it.
## When to use
Use this skill when, on macOS, the user wants to read/query/export **their own**
local WeChat data and a key-extraction step fails on a modern WeChat build —
symptoms: `提取到 0 个密钥` / "extracted 0 keys", `无法解密 session.db` / "cannot
decrypt", or a memory scan that finds no `x'...'` pattern. Those tools assume
WeChat ≤ 4.1.8; this skill handles 4.x (verified on **4.1.13**).
Only operate on the machine's own logged-in account, at the user's request.
## Why the old method fails on 4.x (one paragraph)
WeChat ≤ 4.1.8 left the SQLCipher raw key in memory as an ASCII PRAGMA string
`x'<64 hex key><32 hex salt>'`, so tools scanned for it. WeChat 4.x no longer
keeps that string resident, and the derived keys are not reliably recoverable as
aligned raw bytes either. **Each database now has its own key**
`enc_key_i = PBKDF2-HMAC-SHA512(password, salt_i, 256000, 32)` (salt = first 16
bytes of each file; the account `password` is shared across all DBs). The
reliable, version-robust way to obtain the keys is to hook the system crypto
functions WeChat calls at runtime — see `reference/method.md`.
## Method (what the script does)
Hook macOS **CommonCrypto** with Frida on the live WeChat process:
- `CCCrypt`, `CCCryptorCreate`, `CCCryptorCreateWithMode` — SQLCipher calls these
to AES-decrypt **every database page**, passing the 32-byte derived key. WeChat
reads its DBs constantly, so keys stream in with no special trigger. Each
captured key is verified against every database's page-1 HMAC-SHA512 to map it
to a database.
- `CCKeyDerivationPBKDF` — if a key derivation happens (e.g. a DB is opened during
capture), this yields the **password** directly, letting us derive keys for all
databases at once.
SQLCipher parameters (WeChat 4.x): AES-256-CBC, page size **4096**, reserve **80**
(= 16-byte IV + 64-byte HMAC-SHA512), KDF **PBKDF2-HMAC-SHA512 × 256000**, HMAC
subkey via `PBKDF2(enc_key, salt XOR 0x3a, 2)`, per-database salt.
## Prerequisites (do these first)
1. **Full Disk Access** for the terminal: System Settings → Privacy & Security →
Full Disk Access → add Terminal/iTerm → restart it.
2. **WeChat running and logged in.**
3. **Make WeChat debuggable (once).** If attaching or `task_for_pid` fails,
ad-hoc re-sign WeChat with `get-task-allow`, then fully quit & reopen WeChat:
```bash
sudo codesign --force --sign - \
--entitlements /dev/stdin /Applications/WeChat.app <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>com.apple.security.get-task-allow</key><true/>
</dict></plist>
EOF
```
Re-signing is local and reversible (reinstall WeChat to revert). It does not
affect the account.
4. **Install deps** (note the pins):
```bash
python3 -m pip install 'frida<17' pycryptodome
```
## Steps
1. Ensure prerequisites above.
2. Run the extractor as root:
```bash
sudo python3 scripts/extract_key.py # auto-detects db_storage
# or: sudo python3 scripts/extract_key.py --db-dir "<...>/db_storage" --wait 240
```
3. **While it runs, interact with WeChat** — open several chats, Contacts,
Favorites, Moments, the sticker panel, and use search. Each feature reads its
database, which makes SQLCipher decrypt pages and thus reveals that DB's key.
You'll see `[FOUND] <db> enc_key=...` lines.
4. It writes `keys.json` = `{ "rel/path.db": { "enc_key": "<hex>", "salt": "<hex>", "size_mb": N }, ... }`.
Databases not read during the window won't be covered — re-run after opening
those features to fill them in.
5. To also produce decrypted, plaintext SQLite files:
```bash
sudo python3 scripts/extract_key.py --decrypt ./decrypted
```
Then open `./decrypted/message/message_0.db` etc. with any SQLite tool, or use
the bundled `wechat-cli/` to query (it consumes the same key format at
`~/.wechat-cli/all_keys.json`).
## Verifying a key without reading private data
Decrypt only page 1 and check the SQLite header — no chat content is exposed:
```python
from scripts.extract_key import decrypt_page # header should be b"SQLite format 3\x00"
```
## Output contract
`keys.json`: object keyed by database path relative to `db_storage`, each value
`{ "enc_key": <64 hex = 32-byte AES-256 key, used directly>, "salt": <32 hex>,
"size_mb": <float> }`. `enc_key` is the final AES key (no further derivation).
## Troubleshooting
- **`aes_keys_seen` stays 0** → WeChat didn't call CommonCrypto during capture, or
attach didn't take. Confirm sudo + re-signed WeChat; keep clicking in WeChat.
- **frida `ObjC` / bridge / attach oddities** → you're on frida ≥ 17. Pin `frida<17`.
- **`task_for_pid failed`** → do the re-sign step, then fully quit & reopen WeChat.
- **pip TLS errors behind a filtering proxy** → add
`--trusted-host pypi.org --trusted-host files.pythonhosted.org`.
- **Keys extract but decryption looks wrong** → confirm page size 4096 / reserve 80;
a future WeChat could change these (see `reference/method.md`).
## Safety / scope
Local, read-only with respect to WeChat's data. Operate only on the user's own
machine and account, at their explicit request. Never upload databases or keys.
See `README.md` disclaimer.