Decrypt WhatsAPP's Crypt12 Databases
Decrypt WhatsApp's msgstore.db.crypt12 chat database files with Python 3. This script is a Python 3, and requires pycrypto and pycryptodome modules.
You'll need the database file, which is called msgstore.db.crypt12, the decryption key, called simply key and the script below.
Setup
With Python 3 installed, install the required modules:
pip3 install pycrypto
pip3 install pycryptodome
Put the .crypt12 and the key files in the same folder and run the script, like this:
MTeam7 [~] $ python3 decrypt12.py <KEY-FILE> <DATABASE.CRYPT12> <SQLLITE-OUTPUT.DB>
MTeam7 [~] $ python3 decrypt12.py key msgstore.db.crypt12
Decryption of crypt12 file was successful.
MTeam7 [~] $ ls msgstore.db
504K -rw-r--r-- 1 aegon staff 504K Aug 16 11:56 msgstore.db
The database file format is SQLite. We can dump into a decrypt12.py.zip file like this:
MTeam7 [~] $ sqlite3 msgstore.db
SQLite version 3.16.0 2016-11-04 19:09:39
Enter ".help" for usage hints.
sqlite> .output plaintext.sql
sqlite> .dump
sqlite> .quit
MTeam7 [~] $ head -n 3 plaintext.sql
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE messages (_id INTEGER PRIMARY KEY AUTOINCREMENT, key_remote_jid TEXT NOT NULL, key_from_me INTEGER, key_id TEXT NOT NULL, status INTEGER, needs_push INTEGER, data TEXT, timestamp INTEGER, media_url TEXT, media_mime_type TEXT, media_wa_type TEXT, media_size INTEGER, media_name TEXT, media_caption TEXT, media_hash TEXT, media_duration INTEGER, origin INTEGER, latitude REAL, longitude REAL, thumb_image TEXT, remote_resource TEXT, received_timestamp INTEGER, send_timestamp INTEGER, receipt_server_timestamp INTEGER, receipt_device_timestamp INTEGER, read_device_timestamp INTEGER, played_device_timestamp INTEGER, raw_data BLOB, recipient_count INTEGER, participant_hash TEXT, starred INTEGER, quoted_row_id INTEGER, mentioned_jids TEXT, multicast_id TEXT, edit_version INTEGER, media_enc_hash TEXT, payment_transaction_id TEXT, forwarded INTEGER, preview_type INTEGER, send_count INTEGER);
Download
Don't forget to edit Python's path in the shebang (the first line of the script) with the correct Python 3 binary path.
Copy the script below or download it here: decrypt12.py.zip
decrypt12.py
#!/usr/local/Cellar/python/3.7.4/bin/python3
from Crypto.Cipher import AES
import os
import sys
import zlib
def keyfile(kf):
global t1, key
if os.path.isfile(kf) == False:
quit('The specified input key file does not exist.')
elif os.path.getsize(kf) != 158:
quit('The specified input key file is invalid.')
with open(kf, 'rb') as keyfile:
keyfile.seek(30)
t1 = keyfile.read(32)
keyfile.seek(126)
key = keyfile.read(32)
return True
def decrypt12(cf, of):
global t2, iv
if os.path.isfile(cf) == False:
quit('The specified input crypt12 file does not exist.')
tf = cf+'.tmp'
with open(cf, 'rb') as crypt12:
crypt12.seek(3)
t2 = crypt12.read(32)
if t1 != t2:
quit('Key file mismatch or crypt12 file is corrupt.')
crypt12.seek(51)
iv = crypt12.read(16)
crypt12.seek(67)
primer(tf, crypt12, 20)
cipher = AES.new(key, AES.MODE_GCM, iv)
sqlite = zlib.decompress(cipher.decrypt(open(tf, 'rb').read()))
with open(of, 'wb') as msgstore:
msgstore.write(sqlite)
msgstore.close()
os.remove(tf)
return True
def primer(tf, crypt12, sb):
with open(tf, 'wb') as header:
header.write(crypt12.read())
header.close()
with open(tf, 'rb+') as footer:
footer.seek(-sb, os.SEEK_END)
footer.truncate()
footer.close()
def validate(ms):
with open(ms, 'rb') as msgstore:
if msgstore.read(6).decode('ascii').lower() != 'sqlite':
os.remove(ms)
msg = 'Decryption of crypt12 file has failed.'
else:
msg = 'Decryption of crypt12 file was successful.'
msgstore.close()
quit(msg)
def main():
if len(sys.argv) > 2 and len(sys.argv) < 5:
if len(sys.argv) == 3:
outfile = 'msgstore.db'
else:
outfile = sys.argv[3]
if keyfile(sys.argv[1]) and decrypt12(sys.argv[2], outfile):
validate(outfile)
else:
print('\nWhatsApp Crypt12 Database Decrypter' + '\n')
print('\tUsage: python '+str(sys.argv[0])+' key msgstore.db.crypt12 msgstore.db\n')
if __name__ == "__main__":
main()
{{ 'Comments (%count%)' | trans {count:count} }}
{{ 'Comments are closed.' | trans }}