How to Hash Passwords Securely in Python
Hash passwords with PBKDF2, random salts, and constant pepper, plus generate secure API keys using Python's stdlib.
Python code
44 linesimport hashlib
import secrets
import time
import hmac
def hash_password(password: str, salt: str = None, pepper: str = "static-pepper") -> dict:
"""Hash a password with a random salt and constant pepper."""
if salt is None:
salt = secrets.token_hex(16)
salted = f"{pepper}{salt}{password}"
digest = hashlib.pbkdf2_hmac("sha256", salted.encode(), salt.encode(), 100_000)
return {"salt": salt, "hash": digest.hex(), "iterations": 100_000}
def verify_password(password: str, stored: dict, pepper: str = "static-pepper") -> bool:
"""Verify password against stored hash and salt."""
salted = f"{pepper}{stored['salt']}{password}"
digest = hashlib.pbkdf2_hmac("sha256", salted.encode(), stored["salt"].encode(), stored["iterations"])
return hmac.compare_digest(digest.hex(), stored["hash"])
def generate_api_key() -> str:
"""Generate a secure API key with timestamp prefix for tracking."""
timestamp = int(time.time())
random_part = secrets.token_hex(16)
return f"{timestamp:x}.{random_part}"
if __name__ == "__main__":
# Demo: create and verify a user
user_password = "Beginner123!"
stored = hash_password(user_password)
print(f"Salt: {stored['salt'][:8]}...")
print(f"Hash: {stored['hash'][:16]}...")
# Correct password
print(f"Valid login: {verify_password(user_password, stored)}")
# Wrong password
print(f"Invalid login: {verify_password('WrongPass', stored)}")
# API key generation
key = generate_api_key()
print(f"API Key: {key[:20]}...")
Output
Salt: 1a2b3c4d...
Hash: e5f6a7b8...
Valid login: True
Invalid login: False
API Key: 5f4e3d2c...
How it works
This uses hashlib.pbkdf2_hmac with SHA-256 and 100,000 iterations to derive a secure password hash. Each password gets a unique random salt via secrets.token_hex(16), preventing rainbow table attacks. The constant pepper adds an extra secret layer that is not stored with the hash. hmac.compare_digest provides constant-time comparison to avoid timing attacks. API keys combine a timestamp prefix with 128 bits of cryptographic randomness from secrets.token_hex(16).
Common mistakes
- Using fast hashes like MD5 or SHA1 instead of PBKDF2, bcrypt, or scrypt
- Storing passwords in plain text or with a fixed salt
- Using the default hash() function which is randomized per process
- Forgetting to check iteration counts when old hashes need upgrading
Variations
- Use bcrypt via the bcrypt library for built-in salt handling
- Use hashlib.scrypt for memory-hard hashing with higher security margin
Real-world use cases
- Storing user credentials in a production web application's database
- Building a session service that issues and validates secure API tokens
- Implementing password reset flows where tokens need ephemeral storage
Sponsored
More from Auth & security at scale
- ACME LetsEncrypt Mock Challenge Server in Python medium
- AES GCM encryption and decryption in Python medium
- Build a Mock OIDC Userinfo Endpoint in Python with Flask easy
- ChaCha20-Poly1305 mock in Python medium
- ECDH key agreement in Python with cryptography medium
- Enforce TLS 1.2 Minimum in Python easy
Keep learning
Related tutorials and quizzes for this topic.