What is the BCryptPasswordEncoder work factor (strength), what does it control, and how do you choose it?
answer
- 2^strength iterations, exponential
- default 10, range 4-31, +1 doubles cost
- target ~100-250ms per hash
- cost + salt stored inside $2a$10$...
- 72-byte input limit
basics
~20 sThe work factor (strength) sets how many hashing rounds BCrypt does: 2^strength iterations. Higher strength means slower hashing and harder brute force. The default is 10; typical range is 10-12. It's stored inside the hash.
solid answer
~40 sBCryptPasswordEncoder's strength (a.k.a. cost or log-rounds) controls the number of internal key-expansion iterations: 2^strength. Default is 10, valid range 4-31, and each +1 doubles the CPU cost. You tune it so a single hash takes roughly 100-250ms on your hardware — slow enough to make offline brute force expensive, fast enough not to hurt login latency or enable easy denial-of-service. The strength is embedded in the hash ($2a$10$...), so verification automatically uses the strength the password was created with; you don't need to remember it. That also means raising the default doesn't retroactively re-hash old passwords. BCrypt generates a random 16-byte salt per password and truncates input at 72 bytes, so very long passwords or naive pepper-prepending can silently lose entropy.
code
java · 9 lines@Bean
PasswordEncoder passwordEncoder() {
// strength 12: 4x slower than the default 10
return new BCryptPasswordEncoder(12);
}
// $2a$12$... -> the 12 is the cost, embedded in every hash,
// so matches() reuses it automatically:
// encoder.matches("pw", "$2a$12$....")go deeper
Know that higher strength = slower = harder to crack, default 10.
Explain 2^strength, the latency budget, and that cost+salt live in the hash.
Discuss DoS trade-offs, the 72-byte limit, and non-retroactive strength changes.
Own a hardware-benchmarked cost policy plus a rehash-on-login upgrade path and rate limiting.
## What 'work factor' means `BCryptPasswordEncoder` is constructed with a `strength` parameter (also called cost or log-rounds). BCrypt runs an expensive key-setup loop `2^strength` times. So: - strength 10 → 2^10 = 1024 iterations (the default) - strength 11 → 2048 iterations (2× slower) - strength 12 → 4096 iterations (4× slower than 10) Each increment **doubles** the work — it is exponential, not linear. Valid values are **4 to 31**; Spring's default is **10**. ```java new BCryptPasswordEncoder(); // strength 10 new BCryptPasswordEncoder(12); // strength 12 new BCryptPasswordEncoder(BCryptVersion.$2B, 12, secureRandom); // full control ``` ## Why you tune it The whole point of a password hash is to be **deliberately slow** so an attacker who steals the hash database cannot test billions of guesses per second. You pick the highest strength whose latency your login flow can tolerate — commonly targeting ~100-250 ms per hash on your production CPU. Too low: cheap to brute force. Too high: slow logins, and an attacker can weaponize it for CPU-exhaustion denial-of-service by spamming login attempts. ## The hash format encodes the parameters A BCrypt hash looks like: ``` $2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG └┬┘ └┬┘ └──────────────── salt + hash (Base64, 22 + 31 chars) ─┘ ver cost ``` - `$2a$` / `$2b$` / `$2y$` — the algorithm version. - `10` — the **cost/strength**. - next 22 chars — the random **salt** (16 bytes, auto-generated per password). - remaining 31 chars — the actual hash. Because the cost and salt live inside the stored value, `matches()` reproduces the exact computation without you tracking anything. It also means **changing the constructor's strength only affects newly encoded passwords**; existing hashes keep their original cost until re-hashed. ## Gotchas - **72-byte input limit**: BCrypt ignores bytes beyond 72. If you pre-hash or prepend a long pepper, you can push the real password past the cutoff and lose entropy. A common safe pattern for very long inputs is to pre-hash with SHA-256 and Base64-encode before BCrypt — but only if deliberately designed. - **NUL byte truncation** existed in some legacy BCrypt implementations (the `$2a$` bug); Spring's `$2b$`/`$2y$` versions address it. - **Salt is automatic** — never pass your own; BCrypt uses a `SecureRandom` salt embedded in the output, so identical passwords hash differently. - **Raising the default is not retroactive** — combine with `upgradeEncoding` and a `UserDetailsPasswordService` to re-hash old low-cost hashes on next login. - Benchmark on the **actual production hardware**; a strength that is fine on a laptop may be too slow (or too fast) on a constrained container.
- If you increase the strength from 10 to 12 in your bean, do existing users' passwords become stronger automatically?No. Existing hashes retain the cost baked into them ($2a$10$). Only passwords encoded after the change use strength 12. To upgrade old ones you rely on upgradeEncoding()/UserDetailsPasswordService to re-hash them on the user's next successful login.
- Why can a very high BCrypt strength become a denial-of-service vector?Each hash costs 2^strength CPU work. An attacker flooding the login endpoint forces the server to burn that CPU per attempt, exhausting resources. You balance strength with rate limiting and a latency budget rather than maxing it out.
saying these in an interview costs you the question
- Saying higher strength adds a linear rather than exponential cost
- Thinking you must store the strength/salt separately from the hash
- Claiming raising the strength re-hashes existing passwords automatically
- Not knowing BCrypt truncates input at 72 bytes
- Passing a custom salt to BCrypt