forked from Thearas/wechat-db-decrypt-macos
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecrypt_db.py
More file actions
151 lines (122 loc) · 4.3 KB
/
decrypt_db.py
File metadata and controls
151 lines (122 loc) · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/env python3
"""
Decrypt WeChat SQLCipher databases to plaintext SQLite files.
Requirements:
brew install sqlcipher
Usage:
python3 decrypt_db.py # decrypt all databases
python3 decrypt_db.py -o ./decrypted # specify output directory
"""
import json
import os
import subprocess
import sys
import glob
import argparse
DB_DIR = os.path.expanduser(
"~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files"
)
PAGE_SZ = 4096
SALT_SZ = 16
def find_db_dir():
pattern = os.path.join(DB_DIR, "*", "db_storage")
candidates = glob.glob(pattern)
if len(candidates) == 1:
return candidates[0]
if len(candidates) > 1:
return candidates[0]
if os.path.isdir(DB_DIR) and os.path.basename(DB_DIR) == "db_storage":
return DB_DIR
return None
def find_sqlcipher():
brew_path = "/opt/homebrew/opt/sqlcipher/bin/sqlcipher"
if os.path.isfile(brew_path):
return brew_path
for p in os.environ.get("PATH", "").split(":"):
candidate = os.path.join(p, "sqlcipher")
if os.path.isfile(candidate):
return candidate
return None
def decrypt_database(sqlcipher_bin, src_path, dst_path, key_hex):
"""Decrypt a SQLCipher database to a plaintext SQLite file."""
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
# Remove existing decrypted file if present
if os.path.exists(dst_path):
os.remove(dst_path)
sql_commands = f"""PRAGMA key = "x'{key_hex}'";
PRAGMA cipher_page_size = 4096;
ATTACH DATABASE '{dst_path}' AS plaintext KEY '';
SELECT sqlcipher_export('plaintext');
DETACH DATABASE plaintext;
"""
try:
result = subprocess.run(
[sqlcipher_bin, src_path],
input=sql_commands,
capture_output=True,
text=True,
timeout=120,
)
if result.returncode != 0 or "Error" in result.stderr:
return False, result.stderr.strip()
# Verify the decrypted file
if not os.path.isfile(dst_path) or os.path.getsize(dst_path) == 0:
return False, "output file is empty"
return True, "OK"
except subprocess.TimeoutExpired:
return False, "timeout"
except Exception as e:
return False, str(e)
def main():
parser = argparse.ArgumentParser(description="Decrypt WeChat databases")
parser.add_argument(
"--keys",
default="wechat_keys.json",
help="Path to wechat_keys.json (default: wechat_keys.json)",
)
parser.add_argument(
"-o",
"--output",
default="decrypted",
help="Output directory for decrypted databases (default: decrypted)",
)
args = parser.parse_args()
if not os.path.isfile(args.keys):
print(f"[-] Key file not found: {args.keys}")
sys.exit(1)
with open(args.keys, "r") as f:
data = json.load(f)
sqlcipher_bin = find_sqlcipher()
if not sqlcipher_bin:
print("[-] sqlcipher not found. Install it with: brew install sqlcipher")
sys.exit(1)
print(f"[*] Using sqlcipher: {sqlcipher_bin}")
db_dir = find_db_dir()
if not db_dir:
print(f"[-] Could not find db_storage directory under {DB_DIR}")
sys.exit(1)
print(f"[*] DB storage: {db_dir}")
entries = {k: v for k, v in data.items() if not k.startswith("__")}
print(f"[*] Decrypting {len(entries)} databases to {args.output}/\n")
passed = 0
failed = 0
for db_rel_path, key_hex in sorted(entries.items()):
src = os.path.join(db_dir, db_rel_path)
dst = os.path.join(args.output, db_rel_path)
if not os.path.isfile(src):
print(f" ⏭️ {db_rel_path}: source file not found, skipping")
continue
success, detail = decrypt_database(sqlcipher_bin, src, dst, key_hex)
if success:
dst_size = os.path.getsize(dst)
print(f" ✅ {db_rel_path} -> {dst} ({dst_size / 1024:.0f} KB)")
passed += 1
else:
print(f" ❌ {db_rel_path}: {detail}")
failed += 1
print(f"\n[*] Done: {passed} decrypted, {failed} failed")
if passed > 0:
print(f"[*] Decrypted files saved to: {os.path.abspath(args.output)}/")
print(f"[*] You can now run: python3 export_messages.py")
if __name__ == "__main__":
main()