← Back to Blog
2026-02-233 min readby DevUtilz

Bcrypt Password Hashing Guide

SecurityBcryptPasswordTutorial

Bcrypt Password Hashing Guide

Bcrypt is the gold standard for password hashing. Learn how to implement it correctly in your applications.

Why Bcrypt?

Bcrypt is designed to be slow and memory-intensive, making it resistant to brute-force attacks. Key features:

  • Adaptive: Work factor can be increased over time
  • Salt included: Each password gets unique salt
  • Proven: Used by millions of applications worldwide

Basic Implementation

Installation

npm install bcrypt

Hashing Passwords

const bcrypt = require('bcrypt');

async function hashPassword(password) {
  const saltRounds = 12; // Higher = slower but more secure
  const hash = await bcrypt.hash(password, saltRounds);
  return hash;
}

Verifying Passwords

async function verifyPassword(password, hash) {
  const match = await bcrypt.compare(password, hash);
  return match;
}

Understanding the Hash

A bcrypt hash looks like this:

$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyYqKx7pIqGi

Parts:

  • $2b$ - Algorithm version
  • 12$ - Cost factor (2^12 = 4096 iterations)
  • LQv3c1yqBWVHxkd0LHAkCO - Salt (22 characters)
  • Yz6TtxMQJqhN8/LewY5GyYqKx7pIqGi - Hash (31 characters)

Choosing the Right Cost Factor

| Cost | Time (approx) | Recommendation | |------|---------------|----------------| | 10 | ~100ms | Minimum for new apps | | 12 | ~400ms | Recommended default | | 14 | ~1.6s | High security | | 15+ | >3s | Too slow for web |

Start with 12 and adjust based on your performance requirements.

Best Practices

1. Never Store Plain Text Passwords

// Wrong - Never do this!
user.password = password;

// Correct - Always hash
user.password = await bcrypt.hash(password, 12);

2. Use Consistent Error Handling

async function login(email, password) {
  const user = await findUserByEmail(email);
  
  if (!user) {
    return { error: 'Invalid credentials' };
  }
  
  const valid = await bcrypt.compare(password, user.password);
  
  if (!valid) {
    return { error: 'Invalid credentials' };
  }
  
  return { user };
}

3. Handle Timing Attacks

// Use bcrypt's compare to prevent timing attacks
const valid = await bcrypt.compare(password, hash);
// Don't use: password === storedHash

4. Implement Rate Limiting

const loginAttempts = new Map();

async function checkRateLimit(ip) {
  const attempts = loginAttempts.get(ip) || 0;
  
  if (attempts >= 5) {
    throw new Error('Too many attempts. Try again later.');
  }
  
  loginAttempts.set(ip, attempts + 1);
  // Reset after 15 minutes
  setTimeout(() => loginAttempts.delete(ip), 900000);
}

Common Mistakes to Avoid

  1. Low cost factor - Don't use cost < 10
  2. No salt - Bcrypt handles this automatically
  3. Storing the password - Store only the hash
  4. Not handling errors - Always wrap in try/catch

Advanced: Custom Raw Hash

const salt = await bcrypt.genSalt(12);
const hash = await bcrypt.hash(password, salt);

// Or use the promise API
const hash = await bcrypt.hash(password, '$2b$12$customsaltstring');

Migration Strategy

When upgrading cost factor:

async function migratePassword(user, password) {
  // Verify with old hash
  const valid = await bcrypt.compare(password, user.password);
  
  if (valid) {
    // Rehash with new cost factor
    user.password = await bcrypt.hash(password, 12);
    await user.save();
  }
  
  return valid;
}

Conclusion

Bcrypt is the right choice for password hashing in most applications. Use cost factor 12, always hash passwords, and never store plain text. Your users' security depends on it.