Skip to content

Instantly share code, notes, and snippets.

@agners
Last active June 16, 2025 16:16
Show Gist options
  • Save agners/2be849a92202f78489c29a74bb56a19d to your computer and use it in GitHub Desktop.
Save agners/2be849a92202f78489c29a74bb56a19d to your computer and use it in GitHub Desktop.
Extract SecureTar encrypted tar.gz files
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "securetar",
# ]
# ///
import gzip
import io
import os
import tarfile
from pathlib import Path
import hashlib
from securetar import (
SECURETAR_MAGIC,
SecureTarFile,
)
def password_to_key(password: str) -> bytes:
"""Generate an AES Key from a password.
Matches the implementation in supervisor.backups.utils.password_to_key.
"""
key: bytes = password.encode()
for _ in range(100):
key = hashlib.sha256(key).digest()
return key[:16]
def extract_secure_tar(input_file: Path, output_dir: Path, password: str, bufsize: int) -> None:
"""Extracts an encrypted SecureTar file."""
os.makedirs(output_dir, exist_ok=True)
key = password_to_key(password)
with SecureTarFile(input_file, "r", gzip=True, bufsize=bufsize, key=key) as tar_file:
tar_file.extractall(path=output_dir, filter='tar')
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Extract an encrypted SecureTar file.")
parser.add_argument("input_file", type=Path, help="Path to the encrypted SecureTar file.")
parser.add_argument("output_dir", type=Path, help="Directory where the extracted files will be saved.")
parser.add_argument("--password", type=str, required=True, help="Password for the SecureTar file.")
parser.add_argument("--bufsize", type=int, default=10240, help="Buffer size for reading files (default: 10240 bytes).")
args = parser.parse_args()
try:
extract_secure_tar(args.input_file, args.output_dir, args.password, args.bufsize)
print("Extraction completed successfully.")
except Exception as e:
print(f"Error: {e}")
@jkerhin
Copy link

jkerhin commented Jun 15, 2025

Thanks for this gist! I was able to use this to poke at my Home Assistant backup files and satisfy my curiosity.

You can make this into a script that can be run without needing to set up a venv using inline script metadata (PEP-723) with some slight modifications. This would allow you to directly do uv run securetar-extract.py ... or pipx run securetar-extract.py ...

#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
#     "securetar",
# ]
# ///
import gzip
import io
import os
import tarfile
from pathlib import Path
import hashlib
from securetar import (
    SECURETAR_MAGIC,
    SecureTarFile,
)

def password_to_key(password: str) -> bytes:
    """Generate an AES Key from a password.

    Matches the implementation in supervisor.backups.utils.password_to_key.
    """
    key: bytes = password.encode()
    for _ in range(100):
        key = hashlib.sha256(key).digest()
    return key[:16]

def extract_secure_tar(input_file: Path, output_dir: Path, password: str, bufsize: int) -> None:
    """Extracts an encrypted SecureTar file."""
    os.makedirs(output_dir, exist_ok=True)

    key = password_to_key(password)

    with SecureTarFile(input_file, "r", gzip=True, bufsize=bufsize, key=key) as tar_file:
        tar_file.extractall(path=output_dir, filter='tar')

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(description="Extract an encrypted SecureTar file.")
    parser.add_argument("input_file", type=Path, help="Path to the encrypted SecureTar file.")
    parser.add_argument("output_dir", type=Path, help="Directory where the extracted files will be saved.")
    parser.add_argument("--password", type=str, required=True, help="Password for the SecureTar file.")
    parser.add_argument("--bufsize", type=int, default=10240, help="Buffer size for reading files (default: 10240 bytes).")

    args = parser.parse_args()

    try:
        extract_secure_tar(args.input_file, args.output_dir, args.password, args.bufsize)
        print("Extraction completed successfully.")
    except Exception as e:
        print(f"Error: {e}")

@agners
Copy link
Author

agners commented Jun 16, 2025

Nice, thanks for the hint. Extended the gist accordingly 👍 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment