# Storing Passwords — Authentication & Authorization

Source: https://www.skillbyai.com/en/authentication/p-storage

> Slow, salted, adaptive hashes.

## Hash, never encrypt

Passwords must be stored with a **slow, salted, one-way password hashing function**, never encrypted (encryption is reversible) and never with a fast hash like plain SHA-256 or MD5 (GPUs try billions of guesses per second). The OWASP Password Storage Cheat Sheet recommends **Argon2id** first, then **scrypt**, with **bcrypt** for legacy systems and **PBKDF2** where FIPS compliance is required. A unique random **salt** per password defeats precomputed tables and makes identical passwords hash differently; modern libraries generate it and embed it in the output string together with the parameters. Tune the cost so a hash takes a noticeable fraction of a second on your hardware, and raise it over time. Check the cheat sheet for current recommended parameters.

## Store, verify, recover

Passwords are still everywhere. Hash them slowly, verify them carefully and recover accounts without opening new holes.

![Three ideas: password storage, login flow design, reset and verification flows.](assets/figures/authentication/section-2-map.svg) — Figure 2.1 — Hash, verify, recover.

## Argon2id in Python

Using the argon2-cffi library; parameters and salt are encoded in the hash string.

```python
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher()  # Argon2id with library defaults; tune for your hardware

def register(email: str, password: str) -> None:
    hashed = ph.hash(password)        # '$argon2id$v=19$m=...,t=...,p=...$salt$hash'
    db.users.insert(email=email, password_hash=hashed)

def check_password(user, password: str) -> bool:
    try:
        ph.verify(user.password_hash, password)
    except VerifyMismatchError:
        return False
    if ph.check_needs_rehash(user.password_hash):  # parameters were raised
        db.users.update(user.id, password_hash=ph.hash(password))
    return True
```

## Know the bcrypt limits

bcrypt only uses the first 72 bytes of input, and some implementations truncate silently. Prefer Argon2id for new systems, and use a vetted library rather than pre-hashing tricks you design yourself.

**Quiz:** Why is plain SHA-256 a poor choice for password storage?

- [ ] It is reversible with the right key
- [x] It is designed to be fast, so offline guessing is cheap
- [ ] It produces outputs that are too long to store
- [ ] It cannot be used with a salt

*Answer:* It is designed to be fast, so offline guessing is cheap. Password hashes must be deliberately slow and tunable.
