# Password hashing

> Store a slow, salted hash — Argon2id or bcrypt — never the password and never a fast hash.

Concept · Web services · https://levelcode.org/concepts/password-hashing

Assume your user table will leak one day. What's stored in it decides whether attackers get hashes they can't use or every user's password, which they'll try on other sites.

## What not to do

- **Plaintext or reversible encryption:** one leak, or one stolen key, exposes everything.
- **A fast hash like SHA-256 or MD5,** even with a salt. A GPU computes billions of SHA-256 hashes per second, which is enough to crack most human-chosen passwords quickly.

## What to do

Use a **password hashing function**. These are deliberately slow and memory-hard, and they salt each password automatically.

| Algorithm | Notes |
|---|---|
| **Argon2id** | Current first choice (OWASP). Tunable memory, time and parallelism. |
| **bcrypt** | Widely available and still fine. Cost factor 10–12+. Only uses the first 72 bytes of input (Go's package returns an error for longer passwords rather than truncating). |
| **scrypt** | Memory-hard; a reasonable choice where Argon2 isn't available. |

```go
import "golang.org/x/crypto/bcrypt"

hash, err := bcrypt.GenerateFromPassword([]byte(password), 12) // salt is generated and embedded
// store string(hash): "$2a$12$R9h/cIPz0gi.URNNX3kh2O…"

err = bcrypt.CompareHashAndPassword(hash, []byte(attempt))  // constant-time; nil means match
```

The stored string contains the algorithm, the cost and the salt. That means you can **raise the cost later**: when a user logs in with a hash that uses old parameters, rehash the password with the new ones.

## Tuning and the rest of the login flow

- **Aim for roughly 100–500 ms per hash** on your servers. That's slow for an attacker making billions of guesses and unnoticeable for a person logging in once.
- **Since hashing is slow on purpose,** rate-limit login attempts per account and per IP, or the hash becomes an easy CPU denial-of-service target.
- **Return the same error** for "no such user" and "wrong password," and take the same time for both: hash a dummy value when the user doesn't exist.
- **Check new passwords against known breached lists,** prefer length over composition rules, and offer passkeys or MFA.

After a successful check, the request still needs a [session or token](https://levelcode.org/concepts/sessions-vs-tokens). The [auth lesson](https://levelcode.org/roadmaps/go-backend/auth) builds the whole flow.
