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.

Easy Python 3.9+ Aug 9, 2026 Auth & security at scale 15 views 0 copies

Python code

44 lines
Python 3.9+
import 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

stdout
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

  1. Use bcrypt via the bcrypt library for built-in salt handling
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Auth & security at scale

Related tutorials and quizzes for this topic.