Support crypt15

This commit is contained in:
KnugiHK
2022-02-22 18:33:54 +08:00
parent 734bb78cd8
commit 0e6319eb4e
3 changed files with 62 additions and 13 deletions

View File

@@ -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."

View File

@@ -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]

View File

@@ -41,7 +41,8 @@ setuptools.setup(
'bleach'
],
extras_require={
'android_backup': ["pycryptodome"]
'android_backup': ["pycryptodome"],
'crypt15': ["pycryptodome", "javaobj-py3"]
},
entry_points={
"console_scripts": [