Files
2025-11-13 23:56:05 +00:00

138 lines
4.2 KiB
Python

#!/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,
*,
algorithm: str = "pbkdf2",
iterations: int | None = None,
salt_bytes: int = DEFAULT_SALT_BYTES,
) -> tuple[str, str]:
"""Return a base64 encoded salt and hash for ``password``."""
from algorithms import get_algorithm
algo = get_algorithm(algorithm)
return algo.hash(password, iterations=iterations, salt_bytes=salt_bytes)
def verify_password(
password: str,
salt_b64: str,
hash_b64: str,
*,
algorithm: str = "pbkdf2",
iterations: int | None = None,
) -> bool:
"""Validate ``password`` against the provided base64 salt + hash pair."""
from algorithms import get_algorithm
algo = get_algorithm(algorithm)
return algo.verify(password, salt_b64, hash_b64, iterations=iterations)
def _build_parser() -> argparse.ArgumentParser:
from algorithms import list_algorithms
available_algos = ", ".join(list_algorithms())
parser = argparse.ArgumentParser(
description=f"Erstellt sichere Password-Hashes oder verifiziert bestehende Werte. "
f"Verfügbare Algorithmen (--algorithm): {available_algos}"
)
subparsers = parser.add_subparsers(dest="command")
hash_parser = subparsers.add_parser(
"hash", help=f"Erstellt ein Salt und einen Hash (--algorithm: {available_algos})."
)
hash_parser.add_argument("password", help="Klartext-Passwort zum Hashen.")
hash_parser.add_argument(
"--algorithm",
"-a",
choices=list_algorithms(),
default="pbkdf2",
help="Hash-Algorithmus (Standard: pbkdf2)",
)
verify_parser = subparsers.add_parser(
"verify", help=f"Überprüft ein Passwort (--algorithm: {available_algos})."
)
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.")
verify_parser.add_argument(
"--algorithm",
"-a",
choices=list_algorithms(),
default="pbkdf2",
help="Hash-Algorithmus (Standard: pbkdf2)",
)
subparsers.add_parser(
"list-algorithms", help="Zeigt alle verfügbaren Hash-Algorithmen an."
)
return parser
def _normalize_args(argv: Sequence[str] | None) -> list[str]:
"""Erlaube ``python3 salt.py <passwort>`` als Abkürzung für ``hash``."""
if not argv:
return []
# If it starts with a subcommand, leave as-is
if argv[0] in {"hash", "verify", "list-algorithms"}:
return list(argv)
# If it starts with help flags, leave as-is
if argv[0] in {"-h", "--help"}:
return list(argv)
# If it starts with --algorithm or -a, prepend "hash"
# This allows: python3 salt.py --algorithm argon2 mypassword
if argv[0] in {"--algorithm", "-a"}:
return ["hash", *argv]
# Otherwise (plain password), prepend "hash"
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 == "list-algorithms":
from algorithms import list_algorithms
print("Verfügbare Algorithmen:")
for algo in list_algorithms():
print(f" - {algo}")
return 0
if args.command == "verify":
if verify_password(args.password, args.salt, args.hash, algorithm=args.algorithm):
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, algorithm=args.algorithm)
print(f"Salt: {salt}")
print(f"Hash: {hash_value}")
return 0
if __name__ == "__main__":
raise SystemExit(main())