#!/usr/bin/env python3 """PBKDF2 helper utilities for hashing and verifying passwords.""" from __future__ import annotations import argparse import base64 import binascii import hashlib import hmac import os import sys from typing import Sequence DEFAULT_SALT_BYTES = 16 DEFAULT_ITERATIONS = int(os.environ.get("PBKDF2_ITERATIONS", "200000")) def hash_password( password: str, *, iterations: int | None = None, salt_bytes: int = DEFAULT_SALT_BYTES, ) -> tuple[str, str]: """Return a base64 encoded salt and hash for ``password``.""" iterations = iterations or DEFAULT_ITERATIONS salt = os.urandom(salt_bytes) derived = hashlib.pbkdf2_hmac( "sha256", password.encode("utf-8"), salt, iterations, ) return ( base64.b64encode(salt).decode("utf-8"), base64.b64encode(derived).decode("utf-8"), ) def verify_password( password: str, salt_b64: str, hash_b64: str, *, iterations: int | None = None, ) -> bool: """Validate ``password`` against the provided base64 salt + hash pair.""" iterations = iterations or DEFAULT_ITERATIONS try: salt = base64.b64decode(salt_b64, validate=True) stored_hash = base64.b64decode(hash_b64, validate=True) except (binascii.Error, ValueError): return False derived = hashlib.pbkdf2_hmac( "sha256", password.encode("utf-8"), salt, iterations, ) return hmac.compare_digest(derived, stored_hash) def _build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="Erstellt sichere Password-Hashes (PBKDF2, Argon2, bcrypt) oder verifiziert bestehende Werte." ) subparsers = parser.add_subparsers(dest="command") hash_parser = subparsers.add_parser( "hash", help="Erstellt ein Salt und einen Hash für ein Passwort." ) hash_parser.add_argument("password", help="Klartext-Passwort zum Hashen.") verify_parser = subparsers.add_parser("verify", help="Überprüft ein Passwort.") verify_parser.add_argument("password", help="Klartext-Passwort zur Überprüfung.") verify_parser.add_argument("salt", help="Base64-kodiertes Salt.") verify_parser.add_argument("hash", help="Base64-kodierter Hash.") return parser def _normalize_args(argv: Sequence[str] | None) -> list[str]: """Erlaube ``python3 salt.py `` als Abkürzung für ``hash``.""" if not argv: return [] if argv[0] in {"hash", "verify"} or argv[0].startswith("-"): return list(argv) return ["hash", *argv] def main(argv: Sequence[str] | None = None) -> int: parser = _build_parser() arg_list = list(argv) if argv is not None else sys.argv[1:] args = parser.parse_args(_normalize_args(arg_list)) if args.command == "verify": if verify_password(args.password, args.salt, args.hash): print("✓ Passwort korrekt") return 0 print("✗ Passwort falsch") return 1 if args.command != "hash": parser.error('Bitte einen Hash generieren oder "verify" verwenden.') salt, hash_value = hash_password(args.password) print(f"Salt: {salt}") print(f"Hash: {hash_value}") return 0 if __name__ == "__main__": raise SystemExit(main())