Home All Algorithms File Hash bcrypt Verify Hash Blog MD5 SHA-256 SHA-512 FAQ More Tools
Security

Choosing the Right bcrypt Cost Factor in 2026 (OWASP Guide)

📅 2026-05-12 ⏱ 6 min read ← Back to Blog

The bcrypt cost factor is the single most important number in your password storage configuration. Too low and a leaked database becomes a quick win for attackers. Too high and your login page times out. The right value depends on your hardware, your traffic, and the year — yes, the year. The recommended minimum has been creeping upward as CPUs get faster.

The TL;DR for 2026

  • Minimum acceptable for production: cost 10 (was 8 in 2012, 10 in 2018, still 10 today as a hard floor)
  • OWASP recommended baseline: cost 12
  • High-security applications: cost 14+
  • Target hash time: 200-500ms per hash on your production hardware
  • Every increment doubles the work for both you and the attacker

How the cost factor works

bcrypt's cost factor is an exponent. The actual number of internal iterations is 2^cost:

CostIterationsApprox. time on modern CPU
416~1 ms (debugging only)
8256~30 ms
101,024~100 ms
112,048~200 ms
124,096~400 ms (OWASP baseline)
138,192~800 ms
1416,384~1.5 s
1532,768~3 s
312³¹~7 years (don't)

The "approx time" column is wildly hardware-dependent. A 2024 high-end server CPU is roughly 2-3× faster than what's shown here. A 2010 server is 3-5× slower. Always benchmark on your actual production hardware before settling on a value.

The OWASP cheat sheet says...

OWASP's Password Storage Cheat Sheet (2025 update, still current in 2026) recommends:

For legacy systems using bcrypt, use a work factor of 10 or more, with a password limit of 72 bytes. The work factor should be as large as verification server performance will allow.

OWASP also notes that Argon2id is the first-choice algorithm for new systems, and bcrypt is a fallback for environments without Argon2 support. If you're choosing bcrypt today, that's fine — but understand it's the conservative choice, not the leading-edge one.

How to choose YOUR cost factor

The cost factor should make a single password hash take about 200-500 milliseconds on your production login server. Why this range?

Run a benchmark on your production server (or one with the same CPU profile):

// Node.js
const bcrypt = require('bcryptjs');
const start = Date.now();
bcrypt.hashSync('test_password_x', 12);
console.log('cost 12:', Date.now() - start, 'ms');
# Python
import bcrypt, time
start = time.time()
bcrypt.hashpw(b'test_password_x', bcrypt.gensalt(rounds=12))
print('cost 12:', round((time.time() - start) * 1000), 'ms')

If cost 12 takes less than 200ms, try 13. If more than 500ms, drop to 11. Pick the highest cost that stays under 500ms.

Concurrency and capacity planning

Login latency isn't the only concern. Each login burns CPU for the duration of the hash. If your average is 300ms per hash and you handle 100 logins/sec at peak, you need 30 CPU-seconds per second — that's 30 dedicated cores doing nothing but hashing passwords.

Mitigations:

Upgrading existing hashes

If your existing database has bcrypt hashes at cost 10 and you want to migrate to cost 12, you don't need a flag day. The standard pattern is "rehash on successful login":

async function login(username, password) {
  const user = await db.findUser(username);
  if (!user) return false;
  
  const valid = await bcrypt.compare(password, user.passwordHash);
  if (!valid) return false;
  
  // Check if hash needs upgrading
  const currentCost = parseInt(user.passwordHash.substring(4, 6));
  if (currentCost < 12) {
    const newHash = await bcrypt.hash(password, 12);
    await db.updateUserHash(user.id, newHash);
  }
  
  return user;
}

Within a few weeks of normal usage, the majority of active accounts will be upgraded. Inactive accounts can be flagged for forced password reset on next login.

The cost factor over time

Historical bcrypt cost recommendations:

Every year or two, review whether your cost factor still produces 200-500ms hashes on current hardware. If hashing is now under 100ms, increment the cost and start migrating.

The 72-byte gotcha

bcrypt silently truncates passwords at 72 bytes (note: bytes, not characters — a UTF-8 emoji can be 4 bytes). This affects passphrases more than it affects normal passwords, but it's still a footgun.

Standard workaround: pre-hash with SHA-256 before bcrypting:

const sha256 = crypto.createHash('sha256').update(password).digest('base64');
const hash = await bcrypt.hash(sha256, 12);

But OWASP warns this introduces its own subtle issues — null bytes in the SHA-256 output, "password shucking" attacks on systems that store both hashes. If you have passphrases >72 bytes to handle, consider Argon2id, which has no length limit.

Want to see how cost factor affects timing on your own hardware? Use our bcrypt generator — the slider lets you experiment with cost 4 through 15 and shows the actual hashing time for each. A great way to calibrate your production setting.

Our Network