Files
Clarium/docs/adr/0001-password-hashing-pbkdf2.md
T
mroberts de38590f46 docs: add book tracker design spec and auth ADRs
Spec covers data model, API, auth, frontend, error handling, and testing.
ADR-0001 records PBKDF2 + pepper (FIPS) over argon2; ADR-0002 records
revocable server-side sessions over Flask's signed cookie.
2026-10-02 13:38:46 -05:00

47 lines
2.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0001: Password hashing with peppered PBKDF2-HMAC-SHA256
Date: 2026-10-02
Status: Accepted
## Context
Users authenticate with a username and password. The application targets a healthcare firm, so
cryptographic choices should be FIPS 140-approved where practical. The assignment restricts the backend to
standard Python libraries (Flask and psycopg2 are provided by the environment). The runtime container has
0.5 GB of RAM.
Memory-hard algorithms (Argon2id, scrypt) resist GPU cracking better, but neither is FIPS-approved and
Argon2 is not in the Python standard library.
## Decision
Hash passwords with PBKDF2-HMAC-SHA256 from `hashlib`, keyed by a secret pepper:
```
peppered = HMAC-SHA256(key=PEPPER, msg=password_utf8)
derived = hashlib.pbkdf2_hmac("sha256", peppered, salt, 600_000)
stored = "pbkdf2_sha256$600000$<salt_b64>$<derived_b64>"
```
- **Salt:** `secrets.token_bytes(16)` per user (128 bits; NIST SP 800-132 requires ≥ 128).
- **Iterations:** 600,000 (OWASP Password Storage Cheat Sheet figure for PBKDF2-HMAC-SHA256). Stored with
the hash; hashes below the current count are re-derived on the next successful login.
- **Pepper:** a secret held outside the database, applied as an HMAC key (NIST SP 800-63B's "secret salt"
recommendation). A database-only leak does not allow offline guessing. Source: `PASSWORD_PEPPER`
(≥ 32 chars) when set; otherwise a 32-byte random value generated once into `backend/.pepper`
(mode 0600, gitignored) with a startup warning that this fallback is for development only.
- **Comparison:** `hmac.compare_digest` (constant time).
- **Enumeration resistance:** a login for an unknown username verifies against a fixed dummy hash so
response time matches a real account; both failures return the same message.
- **Policy:** 12–1024 characters, no composition rules (NIST SP 800-63B). The upper bound caps CPU cost
per request.
## Consequences
- FIPS-approved primitives only (HMAC-SHA256, PBKDF2); no third-party dependency.
- Each login costs ~0.3–0.5 s of CPU and negligible memory, which fits the container.
- Weaker than Argon2id against GPU attacks on a leaked database; the pepper offsets this, because the
attacker also needs the application secret.
- Losing the pepper invalidates every password. Production must keep it in a secrets manager and add
pepper versioning for rotation (out of scope here).