""" AUTHENTIFICATION — HASHAGE DES MOTS DE PASSE (PBKDF2-HMAC-SHA256) ==================================================================== Format de stockage attendu dans le secret USERS pour un compte migre : "pbkdf2_sha256$$$" Un compte pas encore migre garde son mot de passe en clair comme valeur dans USERS : verify_password() reste compatible avec ce cas (comparaison en temps constant quand meme) pour ne pas bloquer les connexions existantes au deploiement, mais ce n'est qu'un pont temporaire — hash_password() doit etre utilise pour generer la nouvelle valeur a coller dans le secret des que possible, compte par compte. """ import hashlib import hmac import os _ALGO = "pbkdf2_sha256" _ITERATIONS = 600_000 def hash_password(password: str, iterations: int = _ITERATIONS) -> str: """Genere la valeur a stocker dans le secret USERS pour ce mot de passe.""" salt = os.urandom(16) digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations) return f"{_ALGO}${iterations}${salt.hex()}${digest.hex()}" def verify_password(stored_value: str, provided_password: str) -> bool: """Compare en temps constant. Accepte soit un hash pbkdf2_sha256$... (compte migre), soit, a titre transitoire, l'ancien mot de passe en clair encore present dans le secret (compte pas encore migre).""" stored_value = str(stored_value or "") provided_password = str(provided_password or "") if stored_value.startswith(f"{_ALGO}$"): try: _, iterations_str, salt_hex, hash_hex = stored_value.split("$", 3) iterations = int(iterations_str) salt = bytes.fromhex(salt_hex) expected = bytes.fromhex(hash_hex) except (ValueError, IndexError): return False computed = hashlib.pbkdf2_hmac("sha256", provided_password.encode("utf-8"), salt, iterations) return hmac.compare_digest(computed, expected) return hmac.compare_digest(stored_value.encode("utf-8"), provided_password.encode("utf-8"))