Why bcrypt is slow on purpose
Most hash functions are designed to be fast — exactly the wrong property for passwords. If hashing a password takes a microsecond, an attacker with your leaked database can test billions of guesses per second. Bcrypt inverts this with a tunable cost factor: each increment doubles the work, so you can dial hashing to take ~250ms–1s on your hardware. That’s invisible to a user logging in once but devastating to an attacker trying to brute-force millions of hashes. This deliberate slowness — key stretching — is the entire point, and it’s why a fast hash like SHA-256 must never store passwords directly.
Tuning the cost factor over time
The cost factor isn’t set-and-forget. Hardware gets faster every year, so a cost that took 300ms in 2020 is quicker now:
| Cost | Approx. iterations | Rough time (modern CPU) |
|---|---|---|
| 10 | 1,024 | ~100 ms |
| 12 | 4,096 | ~250–400 ms |
| 14 | 16,384 | ~1.5 s |
Pick the highest cost that keeps your login latency acceptable (a common target is ~250ms server-side), and revisit it every couple of years — bumping it as hardware improves. Because each hash embeds its own cost, you can raise the cost for new hashes and transparently upgrade old ones on the user’s next successful login.
The 72-byte trap
Bcrypt silently ignores everything past the first 72 bytes of input. For ASCII that’s 72 characters; for multi-byte UTF-8 (accents, emoji, non-Latin scripts) it can be far fewer. Two real consequences: very long passphrases get truncated (so the tail adds no security), and — more dangerously — if you let users paste huge inputs, two different long passwords sharing a 72-byte prefix become interchangeable. The standard mitigation is to pre-hash with SHA-256 and bcrypt the result (bcrypt(base64(sha256(password)))), which compresses any-length input into a fixed, full-entropy value before bcrypt sees it.
Why every hash is different
Paste the same password twice and you’ll get two different 60-character hashes — that’s correct. Bcrypt generates a random salt per hash and embeds it in the output ($2b$12$<salt><hash>), so identical passwords never collide and precomputed rainbow tables are useless. Verification doesn’t re-guess the salt; it reads the salt straight out of the stored hash and recomputes. Everything here runs locally via the bcryptjs library — your passwords never leave the browser — but for the same reason production tokens should be generated server-side with proper key management, treat this tool as one for development, testing, and learning.