Coverage for postrfp/shared/password.py: 100%
27 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-22 21:34 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-22 21:34 +0000
1import logging
4from argon2 import PasswordHasher, Type as Argon2Type
5from argon2.exceptions import VerifyMismatchError, InvalidHash
7from postrfp import conf
9logger = logging.getLogger(__name__)
12def build_hasher() -> PasswordHasher:
13 """
14 Initialize the Argon2 password hasher with the specified parameters.
15 This function is called during application startup to ensure that the
16 hasher is configured correctly.
17 """
18 return PasswordHasher(
19 time_cost=conf.CONF.argon2_time_cost,
20 memory_cost=conf.CONF.argon2_memory_cost,
21 parallelism=conf.CONF.argon2_parallelism,
22 hash_len=conf.CONF.argon2_hash_bytes,
23 salt_len=conf.CONF.argon2_salt_bytes,
24 type=Argon2Type.ID,
25 )
28def create_signature(password: str) -> str:
29 """
30 Creates a standard Argon2 hash string.
32 @param password: The plain text password string.
33 @return: The full Argon2 hash string (e.g., $argon2id$v=19$m=...,t=...,p=...$salt$hash).
34 """
35 hasher = build_hasher()
36 return hasher.hash(password)
39def validate_hash(password: str, hash_signature: str) -> bool:
40 """
41 Validates a password against a stored Argon2 hash signature.
43 @param password: The plain text password string to be validated.
44 @param hash_signature: The full Argon2 hash string stored.
45 @return: True if the password is correct, False otherwise.
46 """
47 try:
48 # The argon2 library's verify function takes the full hash string
49 # It automatically parses the parameters, salt, and hash
50 # We can use the DEFAULT_HASHER instance, or create one on the fly.
51 # Using DEFAULT_HASHER might be slightly faster if params match,
52 # but creating one ensures parameters from the hash are used.
53 # Let's use the library's check function for robustness.
54 return PasswordHasher().verify(hash_signature, password)
56 except VerifyMismatchError:
57 # Password does not match
58 return False
59 except InvalidHash:
60 logger.error(f"Invalid Argon2 hash format provided: {hash_signature}")
61 return False
62 except Exception as e:
63 logger.error(f"Error validating Argon2 hash: {hash_signature} - {e}")
64 return False
67def dummy_argon2_hash():
68 """Performs a dummy Argon2 hash operation for timing attack mitigation."""
69 try:
70 # Use minimal parameters for speed, but still perform the work
71 dummy_hasher = PasswordHasher(
72 time_cost=1, memory_cost=8, parallelism=1, hash_len=16, salt_len=16
73 )
74 dummy_hasher.hash("dummy_password_for_timing_attack_mitigation")
75 except Exception as e:
76 # Log if the dummy operation fails, but don't crash the login attempt
77 logger.error(f"Dummy Argon2 hash operation failed: {e}")