Okta Bcrypt incident lessons for designing better APIs
Bcrypt truncation and API design
- Many commenters focus on bcrypt’s 72‑byte input limit and silent truncation as a fundamentally bad API design for security‑sensitive code.
- Several languages/libraries expose both “raw bcrypt” and “non‑truncating” variants; criticism is that the unsafe, interoperable one is the default, while safer versions are longer‑named or hidden.
- Suggested alternatives:
- Fail loudly (error/exception) on >72 bytes or on NUL bytes.
- Make the strict/safe API the default, and move legacy/“raw” bcrypt into an explicitly dangerous/hazmat namespace.
- Some defend the current behavior for interoperability and legacy compatibility, but others argue that “compat” is not worth the footgun.
Password hash vs KDF and misuse of bcrypt
- Strong agreement that bcrypt is a password hashing function, not a general key derivation function; it’s designed for short, low‑entropy secrets plus salt.
- Okta’s usage (hashing
userId + username + passwordfor a cache key) is seen as misusing a password hash where a general KDF or plain hash would have been more appropriate. - There’s extended discussion clarifying distinctions:
- Password hashes: slow, salted, produce verifier strings.
- KDFs: derive keys of specific sizes from higher‑entropy inputs, often with different cost tradeoffs.
- Some note the naming confusion in the ecosystem (bcrypt historically called a KDF) and the resulting developer misunderstanding.
Why mix username/userId/password into a cache key?
- Hypotheses:
- Ensure cache entries differ per user and password.
- Auto‑invalidate cached auth data when a password changes.
- Several commenters argue this is overcomplicated and risky: better to store user‑scoped data (e.g., password version or last‑credential‑change timestamp) and/or the bcrypt hash itself, instead of the raw password.
- Others point out that pre‑hashing before bcrypt introduces additional gotchas (NUL handling, encoding).
Was bcrypt the real bug?
- Some argue the deeper bug is algorithmic: treating a hash as a unique key without validating the underlying data, ignoring that any fixed‑width hash can collide.
- Others counter that with a strong 192‑bit bcrypt output, real‑world collision risk is effectively negligible; the practical issue was the truncation behavior, not generic hash collisions.
Library ecosystem and examples
- PHP’s
password_*API is cited as harder to misuse (no expose‑your‑own‑salt hash function, justpassword_hashandpassword_verify). - Other libraries (e.g., Common Lisp’s Ironclad) are praised for explicitly rejecting overly long inputs.
- Rust, Zig, and other ecosystems are discussed as partial successes/partial failures in API design around truncation flags and “non_truncating_*” functions.
Broader lessons and reactions
- Many characterize Okta’s design as a “rookie mistake,” especially for an auth company, and use it to argue that security vendors often employ ordinary generalist developers without deep crypto review.
- The thread repeatedly reinforces: don’t invent ad‑hoc constructions; use well‑designed KDFs (PBKDF2, scrypt, Argon2, HKDF, libsodium primitives) and APIs that fail safely.