# Password Hashing — Django / FastAPI

Source: https://www.skillbyai.com/en/django-fastapi/q-passwords

> Never store plain passwords.

## Salted, slow hashes

Django stores passwords as **salted, deliberately slow hashes** (PBKDF2 by default, with Argon2 and bcrypt available) and verifies them with `check_password`; the iteration count rises with each release to keep pace with hardware. Use the built-in auth system or `make_password` rather than inventing your own hashing. In FastAPI projects, use a maintained library (such as pwdlib or passlib with Argon2) for the same purpose.

## Hashing and verifying a password, run

I ran this script with python manage.py shell --no-imports in the demo project (Django 6.1.1, SQLite). The default algorithm is PBKDF2-SHA256 with 1,500,000 iterations in Django 6.1 and a random salt, so hashing the same password twice gives different hashes; check_password verifies correctly.

```python
from django.contrib.auth.hashers import check_password, make_password

h = make_password("correct horse battery staple")
algo, iterations, salt, digest = h.split("$")
print(algo, "iterations:", iterations, "| salted:", len(salt) > 0)
print("verify right:", check_password("correct horse battery staple", h))
print("verify wrong:", check_password("hunter2", h))
print("same password, different hash:", make_password("x") != make_password("x"))
```

Output:

```
pbkdf2_sha256 iterations: 1500000 | salted: True
verify right: True
verify wrong: False
same password, different hash: True
```

## Consider Argon2

Adding argon2-cffi and listing the Argon2 hasher first upgrades password storage; old hashes are upgraded on next login.

**Quiz:** Why does the same password produce different hashes?

- [ ] Hashes include the current time only
- [ ] The hash function is broken
- [ ] The password changes
- [x] Each hash uses a random salt

*Answer:* Each hash uses a random salt. Salts defeat precomputed attacks.
