Created
July 9, 2020 18:44
-
-
Save jdeluyck/cafb5fff57a823639b3bdb0a8a6b667d to your computer and use it in GitHub Desktop.
Script to decrypt Home Assistant encrypted SecureTar snapshots.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import sys | |
import getopt | |
import hashlib | |
import tarfile | |
import glob | |
import os | |
import shutil | |
from pathlib import Path | |
from cryptography.hazmat.backends import default_backend | |
from cryptography.hazmat.primitives.ciphers import ( | |
Cipher, | |
algorithms, | |
modes, | |
) | |
def _password_to_key(password): | |
password = password.encode() | |
for _ in range(100): | |
password = hashlib.sha256(password).digest() | |
return password[:16] | |
def _generate_iv(key, salt): | |
temp_iv = key + salt | |
for _ in range(100): | |
temp_iv = hashlib.sha256(temp_iv).digest() | |
return temp_iv[:16] | |
class SecureTarFile: | |
def __init__(self, filename, password): | |
self._file = None | |
self._name = Path(filename) | |
self._tar = None | |
self._tar_mode = "r|gz" | |
self._aes = None | |
self._key = _password_to_key(password) | |
self._decrypt = None | |
def __enter__(self): | |
self._file = self._name.open("rb") | |
cbc_rand = self._file.read(16) | |
self._aes = Cipher( | |
algorithms.AES(self._key), | |
modes.CBC(_generate_iv(self._key, cbc_rand)), | |
backend=default_backend(), | |
) | |
self._decrypt = self._aes.decryptor() | |
self._tar = tarfile.open(fileobj=self, mode=self._tar_mode) | |
return self._tar | |
def __exit__(self, exc_type, exc_value, traceback): | |
if self._tar: | |
self._tar.close() | |
if self._file: | |
self._file.close() | |
def read(self, size = 0): | |
return self._decrypt.update(self._file.read(size)) | |
@property | |
def path(self): | |
return self._name | |
@property | |
def size(self): | |
if not self._name.is_file(): | |
return 0 | |
return round(self._name.stat().st_size / 1_048_576, 2) # calc mbyte | |
def _extract_tar(filename): | |
_dirname = '.'.join(filename.split('.')[:-1]) | |
try: | |
shutil.rmtree('_dirname') | |
except FileNotFoundError: | |
pass | |
print(f'Extracting {filename}...') | |
_tar = tarfile.open(name=filename, mode="r") | |
_tar.extractall(path=_dirname) | |
return _dirname | |
def _extract_secure_tar(filename, password): | |
_dirname = '.'.join(filename.split('.')[:-2]) | |
print(f'Extracting secure tar {filename.split("/")[-1]}...') | |
try: | |
with SecureTarFile(filename, password) as _tar: | |
_tar.extractall(path=_dirname) | |
except tarfile.ReadError: | |
print("Unable to extract SecureTar - maybe your password is wrong or the tar is not password encrypted?") | |
sys.exit(5) | |
return _dirname | |
def print_usage(): | |
print(f'{sys.argv[0]} -i <inputfile> -p <password>') | |
def main(): | |
_inputfile = None | |
_password=None | |
try: | |
opts, args = getopt.getopt(sys.argv[1:],"hi:p:") | |
except getopt.GetoptError: | |
print_usage() | |
sys.exit(2) | |
for opt, arg in opts: | |
if opt == '-h': | |
print_usage() | |
sys.exit() | |
elif opt in ("-i"): | |
_inputfile = arg | |
elif opt in ("-p"): | |
_password = arg | |
if not _inputfile: | |
print ("Missing inputfile") | |
print_usage() | |
sys.exit(3) | |
if not _password: | |
print ("Missing password") | |
print_usage() | |
sys.exit(4) | |
_dirname = _extract_tar(_inputfile) | |
for _secure_tar in glob.glob(f'{_dirname}/*.tar.gz'): | |
_extract_secure_tar(_secure_tar, _password) | |
os.remove(_secure_tar) | |
print("Done") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This has been copied from https://community.home-assistant.io/t/backup-snapshots-no-longer-decryptable/104048/12 - putting it here so it gets a bit more 'out in the open'. All kudos to the original coder.