mirror of
https://github.com/KnugiHK/WhatsApp-Chat-Exporter.git
synced 2026-02-10 19:22:24 +00:00
Support crypt15
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
from .__init__ import __version__
|
||||
from Whatsapp_Chat_Exporter import extract, extract_iphone
|
||||
from Whatsapp_Chat_Exporter import extract_iphone_media
|
||||
from Whatsapp_Chat_Exporter.extract import Crypt
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import sqlite3
|
||||
import shutil
|
||||
import json
|
||||
import string
|
||||
from sys import exit
|
||||
|
||||
|
||||
@@ -101,14 +103,22 @@ def main():
|
||||
print("You must specify the backup file with -b")
|
||||
exit(1)
|
||||
print("Decryption key specified, decrypting WhatsApp backup...")
|
||||
key = open(options.key, "rb").read()
|
||||
if "crypt12" in options.backup:
|
||||
crypt = Crypt.CRYPT12
|
||||
elif "crypt14" in options.backup:
|
||||
crypt = Crypt.CRYPT14
|
||||
elif "crypt15" in options.backup:
|
||||
crypt = Crypt.CRYPT15
|
||||
if os.path.isfile(options.key):
|
||||
key = open(options.key, "rb").read()
|
||||
elif all(char in string.hexdigits for char in options.key):
|
||||
key = bytes.fromhex(options.key)
|
||||
db = open(options.backup, "rb").read()
|
||||
is_crypt14 = False if "crypt12" in options.backup else True
|
||||
error = extract.decrypt_backup(db, key, msg_db, is_crypt14)
|
||||
error = extract.decrypt_backup(db, key, msg_db, crypt)
|
||||
if error != 0:
|
||||
if error == 1:
|
||||
print("Dependencies of decrypt_backup are not "
|
||||
"present. For details, see README.md.")
|
||||
print("Dependencies of decrypt_backup and/or extract_encrypted_key"
|
||||
" are not present. For details, see README.md.")
|
||||
exit(3)
|
||||
elif error == 2:
|
||||
print("Failed when decompressing the decrypted backup."
|
||||
|
||||
@@ -12,6 +12,7 @@ from pathlib import Path
|
||||
from bleach import clean as sanitize
|
||||
from markupsafe import Markup
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from mimetypes import MimeTypes
|
||||
try:
|
||||
import zlib
|
||||
@@ -20,7 +21,12 @@ except ModuleNotFoundError:
|
||||
support_backup = False
|
||||
else:
|
||||
support_backup = True
|
||||
|
||||
try:
|
||||
import javaobj
|
||||
except ModuleNotFoundError:
|
||||
support_crypt15 = False
|
||||
else:
|
||||
support_crypt15 = True
|
||||
|
||||
def sanitize_except(html):
|
||||
return Markup(sanitize(html, tags=["br"]))
|
||||
@@ -39,18 +45,39 @@ CRYPT14_OFFSETS = [
|
||||
{"iv": 66, "db": 99}
|
||||
]
|
||||
|
||||
|
||||
class Crypt(Enum):
|
||||
CRYPT15 = 15
|
||||
CRYPT14 = 14
|
||||
CRYPT12 = 12
|
||||
|
||||
|
||||
def brute_force_offset():
|
||||
for iv in range(60, 80):
|
||||
for db in range(80, 130):
|
||||
yield iv, iv + 16, db
|
||||
|
||||
def decrypt_backup(database, key, output, crypt14=True):
|
||||
|
||||
def extract_encrypted_key(keyfile):
|
||||
from hashlib import sha256
|
||||
import hmac
|
||||
key_stream = b""
|
||||
for byte in javaobj.loads(keyfile):
|
||||
key_stream += byte.to_bytes(1, "big", signed=True)
|
||||
key = hmac.new(
|
||||
hmac.new(b'\x00' * 32, key_stream, sha256).digest(),
|
||||
b"backup encryption\x01",
|
||||
sha256
|
||||
)
|
||||
return key.digest()
|
||||
|
||||
def decrypt_backup(database, key, output, crypt=Crypt.CRYPT14):
|
||||
if not support_backup:
|
||||
return 1
|
||||
if len(key) != 158:
|
||||
if crypt is not Crypt.CRYPT15 and len(key) != 158:
|
||||
raise ValueError("The key file must be 158 bytes")
|
||||
t1 = key[30:62]
|
||||
if crypt14:
|
||||
if crypt == Crypt.CRYPT14:
|
||||
if len(database) < 191:
|
||||
raise ValueError("The crypt14 file must be at least 191 bytes")
|
||||
current_try = 0
|
||||
@@ -58,16 +85,27 @@ def decrypt_backup(database, key, output, crypt14=True):
|
||||
t2 = database[15:47]
|
||||
iv = database[offsets["iv"]:offsets["iv"] + 16]
|
||||
db_ciphertext = database[offsets["db"]:]
|
||||
else:
|
||||
elif crypt == Crypt.CRYPT12:
|
||||
if len(database) < 67:
|
||||
raise ValueError("The crypt12 file must be at least 67 bytes")
|
||||
t2 = database[3:35]
|
||||
iv = database[51:67]
|
||||
db_ciphertext = database[67:-20]
|
||||
elif crypt == Crypt.CRYPT15:
|
||||
if not support_crypt15:
|
||||
return 1
|
||||
if len(database) < 131:
|
||||
raise ValueError("The crypt15 file must be at least 131 bytes")
|
||||
t1 = t2 = None
|
||||
iv = database[8:24]
|
||||
db_ciphertext = database[131:]
|
||||
if t1 != t2:
|
||||
raise ValueError("The signature of key file and backup file mismatch")
|
||||
|
||||
main_key = key[126:]
|
||||
if crypt == Crypt.CRYPT15:
|
||||
main_key = extract_encrypted_key(key)
|
||||
else:
|
||||
main_key = key[126:]
|
||||
decompressed = False
|
||||
while not decompressed:
|
||||
cipher = AES.new(main_key, AES.MODE_GCM, iv)
|
||||
@@ -75,7 +113,7 @@ def decrypt_backup(database, key, output, crypt14=True):
|
||||
try:
|
||||
db = zlib.decompress(db_compressed)
|
||||
except zlib.error:
|
||||
if crypt14:
|
||||
if crypt == Crypt.CRYPT14:
|
||||
current_try += 1
|
||||
if current_try < len(CRYPT14_OFFSETS):
|
||||
offsets = CRYPT14_OFFSETS[current_try]
|
||||
|
||||
Reference in New Issue
Block a user