Refactored to include functionality in existing DecryptBackup class

This commit is contained in:
Nex 2021-07-30 18:46:45 +02:00
parent bfcfb3aa06
commit b264ae946d
3 changed files with 42 additions and 70 deletions

View File

@ -17,7 +17,6 @@ from mvt.common.module import run_module, save_timeline
from mvt.common.options import MutuallyExclusiveOption from mvt.common.options import MutuallyExclusiveOption
from .decrypt import DecryptBackup from .decrypt import DecryptBackup
from .keyutils import KeyUtils
from .modules.fs import BACKUP_MODULES, FS_MODULES from .modules.fs import BACKUP_MODULES, FS_MODULES
# Setup logging using Rich. # Setup logging using Rich.
@ -55,6 +54,7 @@ def cli():
@click.argument("BACKUP_PATH", type=click.Path(exists=True)) @click.argument("BACKUP_PATH", type=click.Path(exists=True))
def decrypt_backup(destination, password, key_file, backup_path): def decrypt_backup(destination, password, key_file, backup_path):
backup = DecryptBackup(backup_path, destination) backup = DecryptBackup(backup_path, destination)
if password: if password:
backup.decrypt_with_password(password) backup.decrypt_with_password(password)
elif key_file: elif key_file:
@ -63,13 +63,16 @@ def decrypt_backup(destination, password, key_file, backup_path):
raise click.ClickException("Missing required option. Specify either " raise click.ClickException("Missing required option. Specify either "
"--password or --key-file.") "--password or --key-file.")
backup.process_backup()
#============================================================================== #==============================================================================
# Command: extract-key # Command: extract-key
#============================================================================== #==============================================================================
@cli.command("extract-key", help="Extract decryption key from an iTunes backup") @cli.command("extract-key", help="Extract decryption key from an iTunes backup")
@click.option("--password", "-p", @click.option("--password", "-p",
help="Password to use to decrypt the backup", help="Password to use to decrypt the backup",
prompt="Backup password", prompt="Enter backup password",
hide_input=True, prompt_required=False, required=True) hide_input=True, prompt_required=False, required=True)
@click.option("--key-file", "-k", @click.option("--key-file", "-k",
help="Key file to be written (if unset, will print to STDOUT)", help="Key file to be written (if unset, will print to STDOUT)",
@ -77,11 +80,12 @@ def decrypt_backup(destination, password, key_file, backup_path):
type=click.Path(exists=False, file_okay=True, dir_okay=False, writable=True)) type=click.Path(exists=False, file_okay=True, dir_okay=False, writable=True))
@click.argument("BACKUP_PATH", type=click.Path(exists=True)) @click.argument("BACKUP_PATH", type=click.Path(exists=True))
def extract_key(password, backup_path, key_file): def extract_key(password, backup_path, key_file):
key_utils = KeyUtils(password, backup_path) backup = DecryptBackup(backup_path)
backup.decrypt_with_password(password)
backup.get_key()
if key_file: if key_file:
key_utils.write_key(key_file) backup.write_key(key_file)
else:
key_utils.print_key()
#============================================================================== #==============================================================================

View File

@ -18,7 +18,7 @@ class DecryptBackup:
using either a password or a key file. using either a password or a key file.
""" """
def __init__(self, backup_path, dest_path): def __init__(self, backup_path, dest_path=None):
"""Decrypts an encrypted iOS backup. """Decrypts an encrypted iOS backup.
:param backup_path: Path to the encrypted backup folder :param backup_path: Path to the encrypted backup folder
:param dest_path: Path to the folder where to store the decrypted backup :param dest_path: Path to the folder where to store the decrypted backup
@ -26,8 +26,12 @@ class DecryptBackup:
self.backup_path = backup_path self.backup_path = backup_path
self.dest_path = dest_path self.dest_path = dest_path
self._backup = None self._backup = None
self._decryption_key = None
def process_backup(self):
if not os.path.exists(self.dest_path):
os.makedirs(self.dest_path)
def _process_backup(self):
manifest_path = os.path.join(self.dest_path, "Manifest.db") manifest_path = os.path.join(self.dest_path, "Manifest.db")
# We extract a decrypted Manifest.db. # We extract a decrypted Manifest.db.
self._backup.getManifestDB() self._backup.getManifestDB()
@ -69,9 +73,6 @@ class DecryptBackup:
""" """
log.info("Decrypting iOS backup at path %s with password", self.backup_path) log.info("Decrypting iOS backup at path %s with password", self.backup_path)
if not os.path.exists(self.dest_path):
os.makedirs(self.dest_path)
try: try:
self._backup = iOSbackup(udid=os.path.basename(self.backup_path), self._backup = iOSbackup(udid=os.path.basename(self.backup_path),
cleartextpassword=password, cleartextpassword=password,
@ -79,9 +80,6 @@ class DecryptBackup:
except Exception as e: except Exception as e:
log.exception(e) log.exception(e)
log.critical("Failed to decrypt backup. Did you provide the correct password?") log.critical("Failed to decrypt backup. Did you provide the correct password?")
return
else:
self._process_backup()
def decrypt_with_key_file(self, key_file): def decrypt_with_key_file(self, key_file):
"""Decrypts an encrypted iOS backup using a key file. """Decrypts an encrypted iOS backup using a key file.
@ -90,9 +88,6 @@ class DecryptBackup:
log.info("Decrypting iOS backup at path %s with key file %s", log.info("Decrypting iOS backup at path %s with key file %s",
self.backup_path, key_file) self.backup_path, key_file)
if not os.path.exists(self.dest_path):
os.makedirs(self.dest_path)
with open(key_file, "rb") as handle: with open(key_file, "rb") as handle:
key_bytes = handle.read() key_bytes = handle.read()
@ -109,6 +104,31 @@ class DecryptBackup:
except Exception as e: except Exception as e:
log.exception(e) log.exception(e)
log.critical("Failed to decrypt backup. Did you provide the correct key file?") log.critical("Failed to decrypt backup. Did you provide the correct key file?")
def get_key(self):
"""Retrieve and prints the encryption key.
"""
if not self._backup:
return
self._decryption_key = self._backup.getDecryptionKey()
log.info("Derived decryption key for backup at path %s is: \"%s\"",
self.backup_path, self._decryption_key)
def write_key(self, key_path):
"""Save extracted key to file.
:param key_path: Path to the file where to write the derived decryption key.
"""
if not self._decryption_key:
return
try:
with open(key_path, 'w') as handle:
handle.write(self._decryption_key)
except Exception as e:
log.exception(e)
log.critical("Failed to write key to file: %s", key_path)
return return
else: else:
self._process_backup() log.info("Wrote decryption key to file: %s. This file is equivalent to a plaintext password. Keep it safe!",
key_path)

View File

@ -1,52 +0,0 @@
import os
import logging
from iOSbackup import iOSbackup
log = logging.getLogger(__name__)
class KeyUtils:
"""This class provides functions to extract a backup key from a password.
"""
def __init__(self, password, backup_path):
"""Generates a key file for an iOS backup.
:param password: Backup encryption password
:param key_file: Path to the file where to store the generated key file
"""
self.password = password
self.backup_path = backup_path
self._backup = None
def get_key(self):
try:
self._backup = iOSbackup(udid=os.path.basename(self.backup_path),
cleartextpassword=self.password,
backuproot=os.path.dirname(self.backup_path))
except Exception as e:
log.exception(e)
log.critical("Failed to decrypt backup. Did you provide the correct password?")
return
else:
self.decryption_key = self._backup.getDecryptionKey()
log.info("Extracted decryption key.")
def print_key(self):
self.get_key()
log.info("Decryption key for backup at path %s is:\n %s",
self.backup_path, self.decryption_key)
def write_key(self, key_file):
self.get_key()
try:
with open(key_file, 'w') as writer:
writer.write(self.decryption_key)
except Exception as e:
log.exception(e)
log.critical("Failed to write key file.")
return
else:
log.info("Wrote decryption key for backup at path %s to file %s",
self.backup_path, key_file)
log.warn("The file %s is equivalent to a plaintext password. Keep this file safe!",
key_file)