chore: initial commit with existing codebase
This commit is contained in:
111
salt.py
Normal file
111
salt.py
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/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 PBKDF2-Hashes 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())
|
||||
Reference in New Issue
Block a user