#!/usr/bin/env python3 """SHA-256 destruction certificate. Streams the file. Never prints contents.""" from __future__ import annotations import argparse import hashlib import json import os import sys from datetime import datetime, timezone def sha256_file(path: str) -> tuple[str, int]: digest = hashlib.sha256() size = 0 with open(path, "rb") as handle: while True: chunk = handle.read(1024 * 1024) if not chunk: break size += len(chunk) digest.update(chunk) return digest.hexdigest(), size def utc_now() -> str: stamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") return stamp[:-3] + "Z" def certificate(name: str, size: int, digest: str, type_name: str) -> dict: return { "issuer": "No Readable Company", "record": "Certificate of destruction", "algorithm": "SHA-256", "digest": digest, "timestamp": utc_now(), "name": name, "size": size, "type": type_name, "note": ( "Proof a specific byte sequence was hashed. " "Not proof every copy is gone. Delete the original on disk." ), } def main() -> int: parser = argparse.ArgumentParser( description="Local SHA-256 destruction certificate. Does not print file contents." ) group = parser.add_mutually_exclusive_group(required=True) group.add_argument("--file", help="Path to hash. Contents are never printed.") group.add_argument( "--text", help="UTF-8 text to hash. Avoid for secrets; prefer --file.", ) parser.add_argument("--out", help="Write the JSON certificate to this path.") args = parser.parse_args() if args.file: path = os.path.abspath(args.file) if not os.path.isfile(path): sys.stderr.write("No file at that path.\n") return 1 digest, size = sha256_file(path) cert = certificate( os.path.basename(path), size, digest, "application/octet-stream", ) else: encoded = args.text.encode("utf-8") cert = certificate( "pasted-text.txt", len(encoded), hashlib.sha256(encoded).hexdigest(), "text/plain", ) payload = json.dumps(cert, indent=2) sys.stdout.write(payload + "\n") if args.out: out_path = os.path.abspath(args.out) os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True) with open(out_path, "w", encoding="utf-8") as handle: handle.write(payload + "\n") return 0 if __name__ == "__main__": raise SystemExit(main())