How does bcrypt store the salt and what does the cost factor mean?
The 60-character bcrypt hash contains the algorithm version, the cost, the 22-character salt and the hash itself, so you store one string. The cost is a power of two: each +1 doubles the work.
Explain it simply
A bcrypt result is a self-describing label. Anyone verifying a password can read from it everything needed to repeat the same calculation, so you need no separate salt column in the database.
Take this value:
$2b$12$wQ6tT1x8nZb7J3d1uK2yUeH4m0v8nQ0L5l6r7s8t9u0v1w2x3y4z5$2b$: the bcrypt version.12$: the cost factor, so 2^12 = 4096 rounds of the key setup.- Next 22 characters: the salt.
- The remaining 31 characters: the hash of the password with that salt.
When you call compare, bcrypt reads the salt and cost out of the stored string, hashes the attempt the same way, and checks for a match.
Example
const bcrypt = require("bcrypt");
async function main() {
const t0 = Date.now();
const hash = await bcrypt.hash("secret-pass", 12);
console.log("took ms:", Date.now() - t0); // roughly 150-400 ms on a laptop
console.log(hash.slice(0, 7)); // $2b$12$
}
main();Choosing the cost: aim for roughly 100 to 300 ms per hash on your server. Too low is weak; too high makes login slow and lets attackers overload the CPU with login requests, so pair it with rate limiting. Each +1 doubles the time.
bcrypt only uses the first 72 bytes of a password, so very long passphrases are cut off.
What to say in the interview
"The bcrypt string embeds version, cost and salt, so one column is enough. Cost is exponential: 12 means 4096 rounds, and each increment doubles the time. I tune it to about a few hundred milliseconds and rate limit login."
Common mistakes
- Storing the salt in a separate column as if bcrypt needed it.
- Choosing a cost so high that login endpoints become easy to overload.