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)
- Full Disk Access for the terminal: System Settings → Privacy & Security → Full Disk Access → add Terminal/iTerm → restart it.
- WeChat running and logged in.
- Make WeChat debuggable (once). If attaching or
task_for_pidfails, ad-hoc re-sign WeChat withget-task-allow, then fully quit & reopen WeChat:
Re-signing is local and reversible (reinstall WeChat to revert). It does not affect the account.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 - Install deps (note the pins):
python3 -m pip install 'frida<17' pycryptodome
Steps
- Ensure prerequisites above.
- Run the extractor as root:
sudo python3 scripts/extract_key.py # auto-detects db_storage # or: sudo python3 scripts/extract_key.py --db-dir "<...>/db_storage" --wait 240 - 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. - 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. - To also produce decrypted, plaintext SQLite files:
Then opensudo python3 scripts/extract_key.py --decrypt ./decrypted./decrypted/message/message_0.dbetc. with any SQLite tool, or use the bundledwechat-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:
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_seenstays 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. Pinfrida<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.