---
title: "Store passwords with a slow hash · Agent Wranglers"
url: https://agentwranglers.se/playbook/application-security/authentication-and-sessions/password-storage
id: APS-11
level: Required
applies: accounts
risk: data
updated: 2026-10-27
build: a2a6bdfed830
tokens: 1093
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Store passwords with a slow hash

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Authentication and sessions](https://agentwranglers.se/playbook/application-security/authentication-and-sessions.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: APS-11
- Level: Required
- Chapter: [Authentication and sessions](https://agentwranglers.se/playbook/application-security/authentication-and-sessions.md)
- Rule: Store passwords only as a slow, salted hash made for passwords, such as Argon2id or bcrypt.
- Check: Look at a test user's row in the database. The password field starts with a marker such as `$argon2id$`, `$scrypt$` or `$2b$`, never the password or a plain hex hash. An Argon2id hash shows `m=19456,t=2,p=1` or higher. On a bcrypt system, a test sets a 100-byte password and then signs in with only its first 72 bytes, and the sign-in fails.
- Risk: Data leaked or lost
- Applies when: The project has user accounts.
- How: Use a maintained password hashing library. OWASP suggests Argon2id with 19 MiB of memory, 2 passes and 1 thread as a minimum. Use scrypt, or PBKDF2 with 600,000 rounds of HMAC-SHA-256 where FIPS rules apply. Use bcrypt with a cost of at least 10 only for older systems. Bcrypt reads only the first 72 bytes and ignores the rest, so two long passwords with the same start match. With bcrypt, refuse passwords over 72 bytes in UTF-8. Or pre-hash them with HMAC-SHA-256 and a secret key, and pass the base64 result to bcrypt. Never use a fast hash such as SHA-256 alone. Rehash old hashes when a user next logs in. A [sign-in provider or library](https://agentwranglers.se/playbook/accounts/sign-in-methods/auth-provider-or-library.md) does this for you.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
// Node with the argon2 package: Argon2id is the default
import argon2 from 'argon2';
const hash = await argon2.hash(password);
const ok = await argon2.verify(hash, attempt);
```

### Python example

```python
# Python with argon2-cffi: Argon2id is the default
from argon2 import PasswordHasher
hasher = PasswordHasher()
hash_ = hasher.hash(password)
hasher.verify(hash_, attempt)  # raises VerifyMismatchError when wrong
if hasher.check_needs_rehash(hash_):
    hash_ = hasher.hash(attempt)
```

## Sources

- [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)

## Related rules

- ACC-6 [Decide whether a provider or a library runs sign-in](https://agentwranglers.se/playbook/accounts/sign-in-methods/auth-provider-or-library.md) in Accounts and identity
- ACC-13 [Accept long passwords and refuse breached ones](https://agentwranglers.se/playbook/accounts/sign-in-methods/password-rules.md) in Accounts and identity
- TKO-18 [Move users without resetting their passwords](https://agentwranglers.se/playbook/codebase-takeover/leaving-an-ai-builder/move-users-with-hashes.md) in Codebase takeover
- APS-54 [Encrypt data on the wire and on disk](https://agentwranglers.se/playbook/application-security/cryptography-and-keys/encrypt-data.md) in Application security
- APS-56 [Use only standard cryptography](https://agentwranglers.se/playbook/application-security/cryptography-and-keys/standard-cryptography.md) in Application security

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/application-security/authentication-and-sessions/password-storage">Agent Wranglers application security playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Slow down password guessing](https://agentwranglers.se/playbook/application-security/authentication-and-sessions/login-guessing.md)
- Next: [Verify every token](https://agentwranglers.se/playbook/application-security/authentication-and-sessions/token-checks.md)
- [Authentication and sessions](https://agentwranglers.se/playbook/application-security/authentication-and-sessions.md)
