diff --git a/Whatsapp_Chat_Exporter/__main__.py b/Whatsapp_Chat_Exporter/__main__.py index feb4763..ee917ca 100644 --- a/Whatsapp_Chat_Exporter/__main__.py +++ b/Whatsapp_Chat_Exporter/__main__.py @@ -40,7 +40,7 @@ def main(): "--backup", dest="backup", default=None, - help="Path to iPhone backup") + help="Path to Android (must be used together with -k)/iPhone WhatsApp backup") parser.add_option( "-o", "--output", @@ -60,6 +60,13 @@ def main(): dest='db', default=None, help="Path to database file") + parser.add_option( + '-k', + '--key', + dest='key', + default=None, + help="Path to key file" + ) (options, args) = parser.parse_args() if options.android and options.iphone: @@ -80,6 +87,16 @@ def main(): msg_db = "msgstore.db" else: msg_db = options.db + if options.key is not None: + if options.backup is None: + print("You must specify the backup file with -b") + return False + print("Decryption key specified, decrypting WhatsApp backup...") + key = open(options.key, "rb").read() + db = open(options.backup, "rb").read() + if not extract.decrypt_backup(db, key, msg_db): + print("Dependencies of decrypt_backup are not present. For details, see README.md") + return False if options.wa is None: contact_db = "wa.db" else: diff --git a/Whatsapp_Chat_Exporter/extract.py b/Whatsapp_Chat_Exporter/extract.py index 0e133d8..877fdb8 100644 --- a/Whatsapp_Chat_Exporter/extract.py +++ b/Whatsapp_Chat_Exporter/extract.py @@ -10,6 +10,13 @@ import re import pkgutil from datetime import datetime from mimetypes import MimeTypes +try: + import zlib + from Crypto.Cipher import AES +except ModuleNotFoundError: + support_backup = False +else: + support_backup = True def determine_day(last, current): @@ -21,6 +28,32 @@ def determine_day(last, current): return current +def decrypt_backup(database, key, output): + if not support_backup: + return False + if len(key) != 158: + raise ValueError("The key file must be 158 bytes") + if len(database) < 191: + raise ValueError("The database file must be at least 191 bytes") + t1 = key[30:62] + t2 = database[15:47] + if t1 != t2: + raise ValueError("The signature of key file and backup file mismatch") + + iv = database[67:83] + db_ciphertext = database[191:] + main_key = key[126:] + cipher = AES.new(main_key, AES.MODE_GCM, iv) + db_compressed = cipher.decrypt(db_ciphertext) + db = zlib.decompress(db_compressed) + if db[0:6].upper() == b"SQLITE": + with open(output, "wb") as f: + f.write(db) + return True + else: + raise ValueError("The plaintext is not a SQLite database. Did you use the key to encrypt something...") + + def contacts(db, data): # Get contacts c = db.cursor()