passwords.py 1.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. import base64
  2. import hashlib
  3. import hmac
  4. import secrets
  5. PBKDF2_ITERATIONS = 260_000
  6. PASSWORD_HASH_PREFIX = "pbkdf2_sha256"
  7. def hash_password(password: str) -> str:
  8. salt = secrets.token_bytes(16)
  9. digest = hashlib.pbkdf2_hmac(
  10. "sha256",
  11. password.encode("utf-8"),
  12. salt,
  13. PBKDF2_ITERATIONS)
  14. return "$".join(
  15. [
  16. PASSWORD_HASH_PREFIX,
  17. str(PBKDF2_ITERATIONS),
  18. base64.b64encode(salt).decode("ascii"),
  19. base64.b64encode(digest).decode("ascii"),
  20. ]
  21. )
  22. def verify_password(password: str, password_hash: str | None) -> bool:
  23. if not password_hash:
  24. return False
  25. try:
  26. algorithm, iterations_raw, salt_raw, digest_raw = password_hash.split("$", 3)
  27. if algorithm != PASSWORD_HASH_PREFIX:
  28. return False
  29. iterations = int(iterations_raw)
  30. salt = base64.b64decode(salt_raw.encode("ascii"))
  31. expected_digest = base64.b64decode(digest_raw.encode("ascii"))
  32. except (ValueError, TypeError):
  33. return False
  34. actual_digest = hashlib.pbkdf2_hmac(
  35. "sha256",
  36. password.encode("utf-8"),
  37. salt,
  38. iterations)
  39. return hmac.compare_digest(actual_digest, expected_digest)