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

1import logging 

2 

3 

4from argon2 import PasswordHasher, Type as Argon2Type 

5from argon2.exceptions import VerifyMismatchError, InvalidHash 

6 

7from postrfp import conf 

8 

9logger = logging.getLogger(__name__) 

10 

11 

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 ) 

26 

27 

28def create_signature(password: str) -> str: 

29 """ 

30 Creates a standard Argon2 hash string. 

31 

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) 

37 

38 

39def validate_hash(password: str, hash_signature: str) -> bool: 

40 """ 

41 Validates a password against a stored Argon2 hash signature. 

42 

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) 

55 

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 

65 

66 

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}")