Extract SQLCipher database keys from WeChat for Mac 4.x (verified on 4.1.13),
so you can decrypt and query your own local chat data — when older tools return
0 keys / cannot decrypt.
This repo is primarily an agent-generic skill (SKILL.md) plus a
self-contained script, and it also bundles a patched wechat-cli so you get a
complete working solution end-to-end.
⚠️ For your own local WeChat account on your own Mac only. All processing is local; nothing is uploaded. See Disclaimer.
# 1) deps (mind the version pins!)
python3 -m pip install 'frida<17' pycryptodome
# 2) one-time: make WeChat debuggable (see "task_for_pid" below), then fully
# quit & reopen WeChat, keep it logged in.
# 3) extract keys (run as root; click around WeChat while it runs)
sudo python3 scripts/extract_key.py --output keys.json
# 4) (optional) also write decrypted, plaintext SQLite copies
sudo python3 scripts/extract_key.py --decrypt ./decryptedkeys.json → { "message/message_0.db": { "enc_key": "<hex>", "salt": "<hex>", "size_mb": N }, ... }.
WeChat encrypts its SQLite databases with SQLCipher (AES-256-CBC).
-
WeChat ≤ 4.1.8 kept the raw key in memory as an ASCII PRAGMA string
x'<64 hex key><32 hex salt>'. Tools (wechat-cli, wechat-decrypt, pywxdump, …) scanned process memory for that pattern. Works great — on old builds. -
WeChat 4.x (e.g. 4.1.13) no longer keeps that string resident. Memory scans find 0 matches. The derived keys are also not reliably recoverable as aligned raw bytes. And each database has a different key:
enc_key_i = PBKDF2-HMAC-SHA512(password, salt_i, 256000, 32)salt_i= first 16 bytes of each.db;password(the account master key) is shared across all databases.
So on 4.x you can't just grep memory. You need to observe the key as WeChat actually uses it.
Attach Frida to the running WeChat process and hook the macOS CommonCrypto functions that SQLCipher calls:
| Hook | Why |
|---|---|
CCCrypt, CCCryptorCreate, CCCryptorCreateWithMode |
SQLCipher AES-decrypts every DB page through these, passing the 32-byte derived key. WeChat reads its DBs constantly, so keys stream in. Each captured key is verified against every DB's page-1 HMAC to map key → database. |
CCKeyDerivationPBKDF |
If a key derivation happens during capture, this yields the password directly → derive all DB keys at once. |
No process restart, no fragile memory layout assumptions, no reliance on
x'...' strings. It degrades gracefully: you get keys for whatever databases
WeChat touches during the capture window (click around to cover more).
Full technical write-up: reference/method.md.
| Parameter | Value |
|---|---|
| Cipher | AES-256-CBC |
| Page size | 4096 bytes |
| Reserve / page | 80 bytes (16-byte IV + 64-byte HMAC-SHA512) |
| KDF | PBKDF2-HMAC-SHA512, 256000 iterations |
| HMAC subkey | PBKDF2-HMAC-SHA512(enc_key, salt XOR 0x3a, 2, 32) |
| Salt | first 16 bytes of each database file |
| Key usage | enc_key is the final AES key (used directly, no extra derivation) |
| Component | Verified | Notes |
|---|---|---|
| WeChat for Mac | 4.1.13 | expected to work across 4.0–4.1.x |
| macOS | 26.6.2 | 13+ should be fine |
| CPU | Apple Silicon (arm64) | Intel (x86_64) supported by the same APIs |
| Python | 3.14.3 | any 3.10+ |
| frida | 16.7.19 | MUST be <17. frida 17 removed built-in JS bridges / changed behavior this relies on. |
| pycryptodome | 3.23.0 | any recent 3.x |
| numpy | (not required here) | only the bundled wechat-cli's legacy memory scanner uses it |
-
Full Disk Access for your terminal: System Settings → Privacy & Security → Full Disk Access → add Terminal/iTerm → restart the terminal.
-
WeChat running and logged in.
-
Make WeChat debuggable (once). If
frida attachortask_for_pidfails, ad-hoc re-sign WeChat withget-task-allow: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
Then fully quit WeChat (⌘Q) and reopen it. Re-signing is local, reversible (reinstall WeChat to revert), and does not affect the account.
# auto-detect db_storage, capture for 240s (default)
sudo python3 scripts/extract_key.py
# explicit paths / longer window
sudo python3 scripts/extract_key.py \
--db-dir "$HOME/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/<wxid>/db_storage" \
--output keys.json --wait 300
# also decrypt matched DBs into ./decrypted as plaintext SQLite
sudo python3 scripts/extract_key.py --decrypt ./decryptedWhile it runs, click around WeChat: open several chats, Contacts, Favorites, Moments, stickers, and use search. Each feature reads its database, revealing that DB's key. Databases not read during the window are simply skipped — re-run after opening them.
wechat-cli/ is a fork of
huohuoer/wechat-cli patched to work on
WeChat 4.x macOS (adds init --method frida, a ctypes memory scanner, and the
CommonCrypto extraction above). It reads keys from ~/.wechat-cli/all_keys.json
(same format as keys.json).
cd wechat-cli
python3 -m venv .venv && . .venv/bin/activate
pip install 'frida<17' -e .
# extract keys straight into ~/.wechat-cli (frida method for 4.x)
sudo .venv/bin/wechat-cli init --force --method frida
sudo chown -R "$(whoami)" ~/.wechat-cli # init runs as root; give the files back to you
# then query (no sudo)
wechat-cli sessions --limit 10
wechat-cli history "name" --limit 20 --format text
wechat-cli search "keyword"
wechat-cli contacts --query "name"| Symptom | Fix |
|---|---|
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; raise --wait. |
frida ObjC undefined / attach weirdness |
You're on frida ≥ 17 — pip install 'frida<17'. |
task_for_pid failed |
Do the re-sign step, then fully quit & reopen WeChat. |
| pip TLS errors behind a filtering proxy | pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org 'frida<17' pycryptodome. |
macOS git/cc say "agree to the Xcode license" |
sudo xcodebuild -license once (unrelated to this tool). |
| keys extract but decryption looks wrong | Confirm page 4096 / reserve 80; a future WeChat could change these (see reference/method.md). |
- huohuoer/wechat-cli — the CLI this bundles/patches (Apache-2.0).
- The
CCKeyDerivationPBKDFhooking idea: nextransit/wechat-4x-key-extraction. - SQLCipher decryption lineage: the
wechat-decryptfamily.
For personal use on your own device and account only. This tool reads locally stored data; it does not send, modify, or delete messages, and nothing leaves your machine. You are responsible for complying with applicable laws and WeChat's terms. Provided as-is, no warranty.
Apache-2.0 (matches the bundled wechat-cli).