Files
password-security-python/salt.py

96 lines
2.9 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:
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 <passwort>`` 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())