mirror of
https://github.com/KnugiHK/WhatsApp-Chat-Exporter.git
synced 2026-05-03 02:43:39 +00:00
Update the way to handle encrypted iOS backup file
Since this commit, iphone_backup_decrypt must be re-installed
This commit is contained in:
@@ -3,9 +3,11 @@
|
|||||||
import shutil
|
import shutil
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
import getpass
|
import getpass
|
||||||
|
import threading
|
||||||
try:
|
try:
|
||||||
from iphone_backup_decrypt import EncryptedBackup, RelativePath
|
from iphone_backup_decrypt import EncryptedBackup, RelativePath, FailedToDecryptError, Domain
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
support_encrypted = False
|
support_encrypted = False
|
||||||
else:
|
else:
|
||||||
@@ -13,60 +15,45 @@ else:
|
|||||||
|
|
||||||
|
|
||||||
def extract_encrypted(base_dir, password):
|
def extract_encrypted(base_dir, password):
|
||||||
backup = EncryptedBackup(backup_directory=base_dir, passphrase=password)
|
backup = EncryptedBackup(backup_directory=base_dir, passphrase=password, cleanup=False, check_same_thread=False)
|
||||||
print("Decrypting WhatsApp database...")
|
print("Decrypting WhatsApp database...")
|
||||||
backup.extract_file(relative_path=RelativePath.WHATSAPP_MESSAGES,
|
try:
|
||||||
|
backup.extract_file(relative_path=RelativePath.WHATSAPP_MESSAGES,
|
||||||
output_filename="7c7fba66680ef796b916b067077cc246adacf01d")
|
output_filename="7c7fba66680ef796b916b067077cc246adacf01d")
|
||||||
backup.extract_file(relative_path=RelativePath.WHATSAPP_CONTACTS,
|
backup.extract_file(relative_path=RelativePath.WHATSAPP_CONTACTS,
|
||||||
output_filename="ContactsV2.sqlite")
|
output_filename="b8548dc30aa1030df0ce18ef08b882cf7ab5212f")
|
||||||
data = backup.execute_sql("""SELECT count()
|
except FailedToDecryptError:
|
||||||
FROM Files
|
print("Failed to decrypt backup: incorrect password?")
|
||||||
WHERE relativePath
|
exit()
|
||||||
LIKE 'Message/Media/%'"""
|
extract_thread = threading.Thread(
|
||||||
)
|
target=backup.extract_files_by_domain,
|
||||||
total_row_number = data[0][0]
|
args=(Domain.WHATSAPP, Domain.WHATSAPP)
|
||||||
print(f"Extracting media...(0/{total_row_number})", end="\r")
|
)
|
||||||
data = backup.execute_sql("""SELECT fileID,
|
extract_thread.daemon = True
|
||||||
relativePath,
|
extract_thread.start()
|
||||||
flags,
|
dot = 0
|
||||||
file
|
while extract_thread.is_alive():
|
||||||
FROM Files
|
print(f"Decrypting and extracting files{'.' * dot}{' ' * (3 - dot)}", end="\r")
|
||||||
WHERE relativePath
|
if dot < 3:
|
||||||
LIKE 'Message/Media/%'"""
|
dot += 1
|
||||||
)
|
time.sleep(0.5)
|
||||||
if not os.path.isdir("Message"):
|
else:
|
||||||
os.mkdir("Message")
|
dot = 0
|
||||||
if not os.path.isdir("Message/Media"):
|
time.sleep(0.4)
|
||||||
os.mkdir("Message/Media")
|
print(f"All required files decrypted and extracted.", end="\n")
|
||||||
i = 0
|
extract_thread.handled = True
|
||||||
for row in data:
|
return backup
|
||||||
destination = row[1]
|
|
||||||
hashes = row[0]
|
|
||||||
folder = hashes[:2]
|
|
||||||
flags = row[2]
|
|
||||||
file = row[3]
|
|
||||||
if flags == 2:
|
|
||||||
try:
|
|
||||||
os.mkdir(destination)
|
|
||||||
except FileExistsError:
|
|
||||||
pass
|
|
||||||
elif flags == 1:
|
|
||||||
decrypted = backup.decrypt_inner_file(file_id=hashes, file_bplist=file)
|
|
||||||
with open(destination, "wb") as f:
|
|
||||||
f.write(decrypted)
|
|
||||||
i += 1
|
|
||||||
if i % 100 == 0:
|
|
||||||
print(f"Extracting media...({i}/{total_row_number})", end="\r")
|
|
||||||
print(f"Extracting media...({total_row_number}/{total_row_number})", end="\n")
|
|
||||||
|
|
||||||
|
|
||||||
def is_encrypted(base_dir):
|
def is_encrypted(base_dir):
|
||||||
with sqlite3.connect(f"{base_dir}/Manifest.db") as f:
|
with sqlite3.connect(os.path.join(base_dir, "Manifest.db")) as f:
|
||||||
c = f.cursor()
|
c = f.cursor()
|
||||||
try:
|
try:
|
||||||
c.execute("""SELECT count()
|
c.execute("""SELECT count()
|
||||||
FROM Files
|
FROM Files
|
||||||
""")
|
""")
|
||||||
|
except sqlite3.OperationalError as e:
|
||||||
|
raise e # These error cannot be used to determine if the backup is encrypted
|
||||||
except sqlite3.DatabaseError:
|
except sqlite3.DatabaseError:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@@ -80,7 +67,8 @@ def extract_media(base_dir):
|
|||||||
print("Read more on how to deal with encrypted backup:")
|
print("Read more on how to deal with encrypted backup:")
|
||||||
print("https://github.com/KnugiHK/Whatsapp-Chat-Exporter/blob/main/README.md#usage")
|
print("https://github.com/KnugiHK/Whatsapp-Chat-Exporter/blob/main/README.md#usage")
|
||||||
return False
|
return False
|
||||||
password = getpass.getpass("Enter the password:")
|
print("Encryption detected on the backup!")
|
||||||
|
password = getpass.getpass("Enter the password for the backup:")
|
||||||
extract_encrypted(base_dir, password)
|
extract_encrypted(base_dir, password)
|
||||||
else:
|
else:
|
||||||
wts_db = os.path.join(base_dir, "7c/7c7fba66680ef796b916b067077cc246adacf01d")
|
wts_db = os.path.join(base_dir, "7c/7c7fba66680ef796b916b067077cc246adacf01d")
|
||||||
@@ -101,8 +89,8 @@ def extract_media(base_dir):
|
|||||||
c = manifest.cursor()
|
c = manifest.cursor()
|
||||||
c.execute(
|
c.execute(
|
||||||
f"""SELECT count()
|
f"""SELECT count()
|
||||||
FROM Files
|
FROM Files
|
||||||
WHERE domain = '{_wts_id}'"""
|
WHERE domain = '{_wts_id}'"""
|
||||||
)
|
)
|
||||||
total_row_number = c.fetchone()[0]
|
total_row_number = c.fetchone()[0]
|
||||||
print(f"Extracting WhatsApp files...(0/{total_row_number})", end="\r")
|
print(f"Extracting WhatsApp files...(0/{total_row_number})", end="\r")
|
||||||
|
|||||||
Reference in New Issue
Block a user