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()
{{ message }}

{{ 'Comments are closed.' | trans }}