How to Hash Passwords and Authenticate Users in Python

A beginner-friendly dataclass-based design that hashes passwords with PBKDF2 and verifies them securely using constant-time comparisons.

Medium Python 3.10+ Aug 9, 2026 Auth & security at scale 16 views 0 copies

Python code

51 lines
Python 3.10+
import hashlib
import hmac
import secrets
from dataclasses import dataclass
from typing import Optional


@dataclass
class User:
    id: int
    username: str
    password_hash: str
    salt: str


def hash_password(password: str) -> tuple[str, str]:
    salt = secrets.token_hex(16)
    password_hash = hashlib.pbkdf2_hmac(
        "sha256", password.encode(), salt.encode(), 100_000
    ).hex()
    return password_hash, salt


def verify_password(password: str, user: User) -> bool:
    computed = hashlib.pbkdf2_hmac(
        "sha256", password.encode(), user.salt.encode(), 100_000
    ).hex()
    return hmac.compare_digest(computed, user.password_hash)


def create_user(user_id: int, username: str, password: str) -> User:
    password_hash, salt = hash_password(password)
    return User(id=user_id, username=username, password_hash=password_hash, salt=salt)


def authenticate(username: str, password: str, users: dict[str, User]) -> Optional[User]:
    user = users.get(username)
    if user and verify_password(password, user):
        return user
    return None


if __name__ == "__main__":
    users_db = {}
    alice = create_user(1, "alice", "S3curePass!")
    users_db[alice.username] = alice

    result = authenticate("alice", "S3curePass!", users_db)
    print(f"Authenticated: {result.username if result else 'failed'}")
    wrong = authenticate("alice", "wrongpass", users_db)
    print(f"Wrong password: {wrong.username if wrong else 'failed'}")

Output

stdout
Authenticated: alice
Wrong password: failed

How it works

This code uses PBKDF2 with SHA-256 and 100,000 iterations to derive a secure password hash, making brute-force attempts computationally expensive. A per-user random salt is generated with secrets.token_hex(16) to prevent rainbow table attacks. The hmac.compare_digest check avoids timing attacks when comparing stored and computed hashes. The dataclass gives a clean, readable structure for user records while plain functions keep authentication logic composable. This pattern mirrors what production frameworks do under the hood.

Common mistakes

  • Using fast hashes like MD5 or SHA-1 instead of a dedicated password hashing function like PBKDF2
  • Storing plaintext passwords or reusing a single salt for all users
  • Comparing hashes with `==` instead of `hmac.compare_digest` when timing matters
  • Hardcoding too few PBKDF2 iterations — use at least 100,000 for SHA-256

Variations

  1. Use `bcrypt` or `argon2-cffi` libraries for even stronger password hashing
  2. Store users in a SQLite or PostgreSQL table instead of an in-memory dict

Real-world use cases

  • Handling user signup and login for a web app where passwords must be stored safely in a database.
  • Building an internal CLI admin tool that authenticates operators before exposing sensitive commands.
  • Testing auth flows in microservice tests by creating fake users with known credentials.

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.