from typing import Sequence from .crypto_pbkdf2_sha256 import pbkdf2_sha256 from .encoding_base64_base64_encode import base64_encode from .encoding_utf8_utf8_encode import utf8_encode def hash_password(password: str, salt: Sequence[int], iterations: int) -> str: """A password as pbkdf2_sha256$$$. The salt is passed in because a capability may not read randomness: pass list(secrets.token_bytes(16)) (or the bytes themselves), fresh for every call. """ if not isinstance(password, str): raise TypeError("password must be a string") if isinstance(salt, (str, dict)) or not hasattr(salt, "__len__"): raise TypeError("salt must be a list of integers from 0 to 255") if len(salt) < 16: raise ValueError("salt must be at least 16 bytes, received %d" % len(salt)) if isinstance(iterations, bool) or not isinstance(iterations, int) or iterations < 1000: raise ValueError("iterations must be a whole number of at least 1000") derived = pbkdf2_sha256(utf8_encode(password), salt, iterations, 32) return "pbkdf2_sha256$%d$%s$%s" % (iterations, base64_encode(salt), base64_encode(derived))