Argon2 password hashing algorithm for Python with secure defaults and multiple variants
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
High-level password hashing interface with the PasswordHasher class, designed for typical password storage and verification use cases with secure defaults.
High-level class for password hashing with sensible defaults. Uses Argon2id by default and generates random salts automatically. Designed for convenience while maintaining security best practices.
class PasswordHasher:
"""
High level class to hash passwords with sensible defaults.
Uses Argon2id by default and uses a random salt for hashing. Can verify
any type of Argon2 as long as the hash is correctly encoded.
"""
def __init__(
self,
time_cost: int = DEFAULT_TIME_COST,
memory_cost: int = DEFAULT_MEMORY_COST,
parallelism: int = DEFAULT_PARALLELISM,
hash_len: int = DEFAULT_HASH_LENGTH,
salt_len: int = DEFAULT_RANDOM_SALT_LENGTH,
encoding: str = "utf-8",
type: Type = Type.ID,
):
"""
Initialize PasswordHasher with specified parameters.
Args:
time_cost: Number of iterations (computation time)
memory_cost: Memory usage in kibibytes
parallelism: Number of parallel threads
hash_len: Length of hash in bytes
salt_len: Length of random salt in bytes
encoding: Text encoding for string passwords
type: Argon2 variant to use
"""from argon2 import PasswordHasher
# Use default parameters (recommended for most applications)
ph = PasswordHasher()
# Or customize for specific security requirements
ph_custom = PasswordHasher(
time_cost=4, # More iterations for higher security
memory_cost=131072, # 128 MiB memory usage
parallelism=2, # Fewer threads for limited environments
hash_len=64, # Longer hash output
)Hash passwords with automatic salt generation and configurable parameters.
def hash(self, password: str | bytes, *, salt: bytes | None = None) -> str:
"""
Hash password and return an encoded hash.
Args:
password: Password to hash (str or bytes)
salt: Optional salt bytes. If None, random salt is generated.
WARNING: Only provide salt if you know what you're doing.
Returns:
Encoded Argon2 hash string
Raises:
argon2.exceptions.HashingError: If hashing fails
"""from argon2 import PasswordHasher
ph = PasswordHasher()
# Hash a password (recommended - automatic salt generation)
password = "my_secure_password"
hash_string = ph.hash(password)
print(hash_string)
# Output: $argon2id$v=19$m=65536,t=3,p=4$base64salt$base64hash
# Hash with custom salt (advanced usage)
import os
custom_salt = os.urandom(16)
hash_with_salt = ph.hash(password, salt=custom_salt)Verify passwords against stored hashes with constant-time comparison to prevent timing attacks.
def verify(self, hash: str | bytes, password: str | bytes) -> Literal[True]:
"""
Verify that password matches hash.
Args:
hash: Encoded hash string (from hash() method)
password: Password to verify
Returns:
True if password matches hash
Raises:
argon2.exceptions.VerifyMismatchError: Password doesn't match
argon2.exceptions.VerificationError: Verification failed for other reasons
argon2.exceptions.InvalidHashError: Hash format is invalid
"""from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher()
# Store this hash in your database
stored_hash = ph.hash("user_password")
# Later, during login verification
try:
ph.verify(stored_hash, "user_password")
print("Login successful!")
except VerifyMismatchError:
print("Invalid password!")Check if stored hashes need to be updated due to changed security parameters.
def check_needs_rehash(self, hash: str | bytes) -> bool:
"""
Check whether hash was created using the instance's parameters.
Use this to detect when passwords should be rehashed due to:
- Changed security parameters
- Updated library defaults
- Security policy changes
Args:
hash: Encoded Argon2 hash string
Returns:
True if hash should be rehashed with current parameters
"""from argon2 import PasswordHasher
ph = PasswordHasher()
# During login, check if hash needs updating
stored_hash = "$argon2id$v=19$m=102400,t=2,p=8$..." # Old parameters
if ph.check_needs_rehash(stored_hash):
# User provided correct password, update hash in database
new_hash = ph.hash(user_password)
# Save new_hash to database
print("Password hash updated to current security standards")Create PasswordHasher instances from parameter objects.
@classmethod
def from_parameters(cls, params: Parameters) -> PasswordHasher:
"""
Construct a PasswordHasher from a Parameters object.
Args:
params: Parameters object with Argon2 configuration
Returns:
PasswordHasher instance configured with params
"""from argon2 import PasswordHasher
from argon2.profiles import RFC_9106_HIGH_MEMORY
# Create hasher from predefined profile
ph = PasswordHasher.from_parameters(RFC_9106_HIGH_MEMORY)
# Or from extracted parameters
from argon2 import extract_parameters
existing_hash = "$argon2id$v=19$m=65536,t=3,p=4$..."
params = extract_parameters(existing_hash)
ph_same = PasswordHasher.from_parameters(params)Access current hasher configuration parameters.
@property
def time_cost(self) -> int:
"""Current time cost (iterations)"""
@property
def memory_cost(self) -> int:
"""Current memory cost (kibibytes)"""
@property
def parallelism(self) -> int:
"""Current parallelism (threads)"""
@property
def hash_len(self) -> int:
"""Current hash length (bytes)"""
@property
def salt_len(self) -> int:
"""Current salt length (bytes)"""
@property
def type(self) -> Type:
"""Current Argon2 type"""from argon2 import PasswordHasher
ph = PasswordHasher()
print(f"Using {ph.type.name} with:")
print(f" Time cost: {ph.time_cost}")
print(f" Memory: {ph.memory_cost} KiB")
print(f" Threads: {ph.parallelism}")
print(f" Hash length: {ph.hash_len} bytes")Install with Tessl CLI
npx tessl i tessl/pypi-argon2-cffi