impl/python/verify_password.py
1,782 bytes · the Python implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import List, Tuple
from .crypto_constant_time_equal import constant_time_equal ← from crypto.constant-time-equal ^1.0.0 · built alongside by fune
from .crypto_pbkdf2_sha256 import pbkdf2_sha256 ← from crypto.pbkdf2-sha256 ^1.0.0 · built alongside by fune
from .encoding_base64_base64_decode import base64_decode
from .encoding_utf8_utf8_encode import utf8_encode
MALFORMED = "stored password hash is malformed"
def _decode_field(text: str) -> List[int]:
try:
data = base64_decode(text)
except ValueError:
raise ValueError(MALFORMED) from None
if len(data) == 0:
raise ValueError(MALFORMED)
return data
def parse_stored_hash(stored: str) -> Tuple[int, List[int], List[int]]:
"""(iterations, salt, hash) of a stored pbkdf2_sha256 string. A string this
code did not write is a data problem to surface, not a wrong password."""
if not isinstance(stored, str):
raise TypeError("stored password hash must be a string")
parts = stored.split("$")
if parts[0] != "pbkdf2_sha256":
raise ValueError("stored password hash is not a pbkdf2_sha256 hash")
if len(parts) != 4:
raise ValueError(MALFORMED)
count = parts[1]
if len(count) == 0 or len(count) > 10 or count[0] == "0" or any(not ("0" <= ch <= "9") for ch in count):
raise ValueError(MALFORMED)
return int(count), _decode_field(parts[2]), _decode_field(parts[3])
def verify_password(password: str, stored: str) -> bool:
"""Does the password match the stored hash? Re-derived with the stored
salt and iteration count, compared in constant time."""
if not isinstance(password, str):
raise TypeError("password must be a string")
iterations, salt, expected = parse_stored_hash(stored)
derived = pbkdf2_sha256(utf8_encode(password), salt, iterations, len(expected))
return constant_time_equal(derived, expected)