DevTools Hub

Search tools

Search for a developer tool

How to Hash Passwords in Python

Part of the Hashing Toolkit
Pattern
hash = PasswordHasher().hash(password)

argon2-cffi — defaults to Argon2id with a random salt generated automatically

Explanation

argon2-cffi (pip install argon2-cffi) is the current recommended choice for Python — it wraps the real, native Argon2 reference implementation and defaults to Argon2id with a random salt generated automatically:

from argon2 import PasswordHasher

ph = PasswordHasher()
hash = ph.hash(password)
ph.verify(hash, password)

The one real gotcha: verify() doesn't return False on a wrong password the way you might expect from a function named verify — it raises argon2.exceptions.VerifyMismatchError instead. Code that calls if ph.verify(hash, password): without a try/except around it will work fine for a correct password and crash with an uncaught exception for an incorrect one, rather than cleanly rejecting the login attempt:

from argon2.exceptions import VerifyMismatchError

try:
    ph.verify(hash, password)
    # password is correct
except VerifyMismatchError:
    # password is wrong
    pass

PasswordHasher() with no arguments defaults to RFC 9106's low-memory profile (64 MiB, 3 iterations, 4 lanes) — already reasonable, and stricter on memory than OWASP's baseline. To set OWASP's exact numbers (m=19456, t=2, p=1) explicitly instead:

ph = PasswordHasher(memory_cost=19456, time_cost=2, parallelism=1)

For what each of these parameters actually does internally, see Argon2 Explained.

Valid examples

  • ph = PasswordHasher() hash = ph.hash(password) # "$argon2id$v=19$m=65536,t=3,p=4$..."

    Defaults to Argon2id with RFC 9106's low-memory profile — the salt and every parameter are embedded in the returned string.

  • try: ph.verify(hash, password) except VerifyMismatchError: pass # wrong password

    verify() raises on mismatch instead of returning False — this is the correct way to handle it.

  • PasswordHasher(memory_cost=19456, time_cost=2, parallelism=1)

    Explicit OWASP-baseline parameters instead of the library's RFC 9106 low-memory defaults.

Invalid examples

  • if ph.verify(hash, password): login()

    verify() doesn't return False on a wrong password — it raises VerifyMismatchError. Without a try/except, a wrong password crashes the request instead of just failing the login.

  • import hashlib hashlib.sha256(password.encode()).hexdigest()

    A raw SHA-256 digest — no salt, no cost factor. See Why SHA-256 Should Not Be Used for Passwords.

  • hash == ph.hash(password) # comparing two fresh hashes

    Every hash gets a new random salt, so hashing the password again and comparing strings will never match the stored hash, even for the correct password — always use verify(), never re-hash-and-compare.

Try it now