56b200d233
New modules: lib/auth, lib/auth_users, lib/db, lib/db_sqlite, lib/password, lib/webauthn, daemon/handlers/auth, scripts/bootstrap_auth, tests/test_auth Frontend: webui/api/auth, hoover/components/auth, pages/login, passkeys, users Updates: daemon/iface and server, lib/common and nginx, pyproject.toml deps, install script, server.py, app.js, and websocket/api clients
84 lines
2.4 KiB
Python
84 lines
2.4 KiB
Python
"""Password hashing with Argon2id.
|
|
|
|
Handles user password storage for the auth DB. Uses argon2-cffi (C
|
|
implementation of the Argon2id memory-hard KDF). Random 16-byte salt is
|
|
generated per hash by the library.
|
|
|
|
Argon2id is used for auth user passwords ONLY. Nginx basic-auth htpasswd
|
|
files continue to use sha256_crypt (passlib) — that's a separate concern
|
|
with different constraints (htpasswd format is standardized).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from argon2 import PasswordHasher
|
|
from argon2.exceptions import InvalidHashError, VerifyMismatchError
|
|
from argon2.low_level import Type
|
|
|
|
# Argon2id: OWASP recommended parameters
|
|
# 64 MiB memory, 3 iterations, 4 parallel threads
|
|
_PH = PasswordHasher(
|
|
time_cost=3,
|
|
memory_cost=65536, # 64 MiB
|
|
parallelism=4,
|
|
hash_len=32,
|
|
salt_len=16,
|
|
type=Type.ID,
|
|
)
|
|
|
|
|
|
def hash_password(password: str) -> str:
|
|
"""Generate an Argon2id hash of *password*.
|
|
|
|
A random 16-byte salt is generated automatically by argon2.
|
|
The resulting hash string starts with ``$argon2id$`` and encodes
|
|
the algorithm version, parameters, salt, and hash output.
|
|
|
|
Args:
|
|
password: Plain-text password string.
|
|
|
|
Returns:
|
|
Full Argon2id hash string (e.g. ``$argon2id$v=19$m=65536,t=3,p=4$...``).
|
|
|
|
Raises:
|
|
TypeError: If *password* is not a string or contains NUL bytes.
|
|
"""
|
|
return _PH.hash(password)
|
|
|
|
|
|
def verify_password(password: str, hash_string: str) -> bool:
|
|
"""Verify *password* against an Argon2id *hash_string*.
|
|
|
|
The hash string must have been produced by :func:`hash_password`.
|
|
|
|
Args:
|
|
password: Plain-text password to verify.
|
|
hash_string: Argon2id hash string to compare against.
|
|
|
|
Returns:
|
|
``True`` if the password matches, ``False`` otherwise.
|
|
|
|
Raises:
|
|
TypeError: If *hash_string* is not a valid Argon2id hash.
|
|
"""
|
|
try:
|
|
_PH.verify(hash_string, password)
|
|
return True
|
|
except (InvalidHashError, VerifyMismatchError, ValueError):
|
|
return False
|
|
|
|
|
|
def needs_rehash(hash_string: str) -> bool:
|
|
"""Check if *hash_string* needs to be rehashed with updated parameters.
|
|
|
|
Returns True if the hash was not produced with the current parameters
|
|
of the hasher, indicating it should be rehashed on next login.
|
|
|
|
Args:
|
|
hash_string: Argon2id hash string to check.
|
|
|
|
Returns:
|
|
``True`` if the hash parameters should be upgraded.
|
|
"""
|
|
return _PH.check_needs_rehash(hash_string)
|