Bcrypt is one of the most trusted password hashing algorithms in the industry, designed specifically to protect user credentials against brute-force attacks. Unlike fast hashing algorithms like MD5 or SHA-256, Bcrypt is intentionally slow and incorporates built-in salt generation, making it ideal for secure password storage. This comprehensive guide explores how Bcrypt works, how to configure it properly, and best practices for implementation.

Table of Contents

Key Takeaways

  • Built-in Salt: Bcrypt automatically generates a unique 128-bit salt for each password, eliminating the need for manual salt management.
  • Adaptive Cost Factor: The work factor (rounds) can be increased over time to keep pace with hardware improvements.
  • Slow by Design: Bcrypt is intentionally computationally expensive, making brute-force attacks impractical.
  • Hash Structure: A Bcrypt hash contains the algorithm version, cost factor, salt, and hash in a single string.
  • Recommended Cost Factor: Use a cost factor of 10-12 for most applications (targeting ~100ms-300ms hash time).
  • Industry Standard: Bcrypt remains a solid choice for password hashing, though Argon2 is recommended for new projects.

Need to generate or verify Bcrypt hashes? Try our free online tool:

Try Bcrypt Generator Tool

What is Bcrypt?

Bcrypt is a password hashing function designed by Niels Provos and David Mazières in 1999, based on the Blowfish cipher. The name "bcrypt" comes from "Blowfish crypt," reflecting its cryptographic foundation.

Why Bcrypt Was Created

Traditional hash functions like MD5 and SHA-1 were designed to be fast, which is great for data integrity checks but terrible for password storage. A fast hash means attackers can try billions of password guesses per second.

Bcrypt solves this by being:

  1. Deliberately Slow: Each hash computation takes significant time
  2. Configurable: The slowness can be adjusted via the cost factor
  3. Salt-Integrated: Each password gets a unique salt automatically

Key Features

Feature Description
Algorithm Based on Blowfish cipher (Eksblowfish)
Output Size 184 bits (24 bytes) hash + 128 bits salt
Salt 128-bit, automatically generated
Cost Factor Configurable (4-31), exponential work increase
String Format $2a$, $2b$, or $2y$ prefix

How Bcrypt Works

Bcrypt's security comes from its unique approach to password hashing. Here's the process:

flowchart TD A[Password Input] --> B[Generate Random Salt] B --> C["Combine Password + Salt"] C --> D[Key Setup Phase] D --> E["Expensive Key Schedule (2^cost iterations)"] E --> F[Encrypt Magic Value] F --> G[Final Hash Output] style A fill:#e1f5fe,stroke:#01579b style B fill:#fff3e0,stroke:#e65100 style E fill:#fce4ec,stroke:#c2185b style G fill:#e8f5e9,stroke:#2e7d32

Step-by-Step Process

  1. Salt Generation: A cryptographically secure 128-bit random salt is generated
  2. Key Setup: The password and salt are used to initialize the Eksblowfish cipher
  3. Expensive Key Schedule: The key schedule is repeated 2^cost times
  4. Encryption: A magic value ("OrpheanBeholderScryDoubt") is encrypted 64 times
  5. Output: The salt and resulting hash are combined into the final string

Why Same Password Produces Different Hashes

A common question is: "Why does hashing the same password twice give different results?"

This is because Bcrypt generates a new random salt for each hash operation. The salt is then embedded in the output string, so verification can extract it and reproduce the same hash.

code
Password: "mypassword"

First hash:  $2b$10$N9qo8uLOickgx2ZMRZoMy.MqrqQb9lYz6H8Kj7OvBOyj5uYjiPWmu
Second hash: $2b$10$ZGdlbGVwaGFudHNhcmVjb.7xJ8L9KjQvMnOpRsTuVwXyZaBcDeFgH

Both are valid hashes of "mypassword" - different salts, same password!

Understanding the Cost Factor

The cost factor (also called "rounds" or "work factor") determines how computationally expensive the hashing process is. It's expressed as a power of 2.

Cost Factor Impact

Cost Factor Iterations Approximate Time*
4 16 ~1ms
8 256 ~10ms
10 1,024 ~100ms
12 4,096 ~300ms
14 16,384 ~1s
16 65,536 ~4s

*Times are approximate and vary by hardware

Choosing the Right Cost Factor

flowchart LR A[Start] --> B{"Hash Time Target?"} B -->|Under 100ms| C["Cost: 10"] B -->|100-300ms| D["Cost: 11-12"] B -->|Over 300ms| E["Cost: 13+"] C --> F["Good for High Traffic"] D --> G["Recommended Balance"] E --> H["Maximum Security"] style D fill:#e8f5e9,stroke:#2e7d32 style G fill:#e8f5e9,stroke:#2e7d32

Recommendations:

  • Development/Testing: Cost factor 4-6 (fast iteration)
  • Production (Standard): Cost factor 10-12 (100-300ms)
  • High Security: Cost factor 13-14 (if UX allows)

Upgrading Cost Factor Over Time

As hardware improves, you should increase the cost factor. Here's a strategy:

javascript
// Check if hash needs upgrading during login
async function loginAndUpgrade(password, storedHash) {
  const isValid = await bcrypt.compare(password, storedHash);
  
  if (isValid) {
    const currentCost = parseInt(storedHash.split('$')[2]);
    const targetCost = 12;
    
    if (currentCost < targetCost) {
      // Rehash with higher cost factor
      const newHash = await bcrypt.hash(password, targetCost);
      await updateUserHash(newHash);
    }
  }
  
  return isValid;
}

Bcrypt Hash Structure Breakdown

A Bcrypt hash string contains all the information needed for verification:

code
$2b$12$N9qo8uLOickgx2ZMRZoMyeKj7OvBOyj5uYjiPWmuabcdefghijk
│ │  │  │                     │
│ │  │  │                     └── Hash (31 characters)
│ │  │  └── Salt (22 characters)
│ │  └── Cost Factor (2 digits)
│ └── Algorithm Version
└── Prefix Marker

Algorithm Versions

Version Description
$2$ Original specification (obsolete)
$2a$ Fixed bugs, most common
$2b$ Fixed unsigned char bug (2014)
$2y$ PHP-specific, equivalent to $2b$

Decoding a Real Hash

Let's break down this hash: $2b$10$vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa

Component Value Meaning
Algorithm $2b$ Bcrypt version 2b
Cost 10 2^10 = 1,024 iterations
Salt vI8aWBnW3fID.ZQ4/zo1G. 22-char Base64 encoded salt
Hash q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa 31-char Base64 encoded hash

Bcrypt vs Other Algorithms

Comparison Table

Feature Bcrypt Argon2 Scrypt PBKDF2
Year 1999 2015 2009 2000
Memory Hard No Yes Yes No
GPU Resistant Moderate High High Low
Parallelism No Configurable No No
OWASP Recommended ✅ (Preferred)
Maturity High Medium High High

When to Use Each

flowchart TD A[Choose Algorithm] --> B{New Project?} B -->|Yes| C{Memory Available?} B -->|No| D["Keep Current if Secure"] C -->|Yes| E[Argon2id] C -->|No| F[Bcrypt] D --> G{"Using MD5 or SHA?"} G -->|Yes| H["Migrate to Bcrypt or Argon2"] G -->|No| I["Increase Cost Factor if Needed"] style E fill:#e8f5e9,stroke:#2e7d32 style F fill:#e8f5e9,stroke:#2e7d32

Summary:

  • New projects: Consider Argon2id (if library support exists)
  • Existing projects: Bcrypt is still excellent
  • Legacy systems: Migrate from MD5/SHA to Bcrypt
  • Resource-constrained: Bcrypt (lower memory requirements)

Code Examples

Node.js (bcryptjs)

javascript
const bcrypt = require('bcryptjs');

// Generate hash
async function hashPassword(password) {
  const saltRounds = 12;
  const hash = await bcrypt.hash(password, saltRounds);
  return hash;
}

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

// Usage
async function main() {
  const password = 'mySecurePassword123';
  
  // Hash the password
  const hash = await hashPassword(password);
  console.log('Hash:', hash);
  // Output: $2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.G5e0oO0oa5m6Wy
  
  // Verify correct password
  const isValid = await verifyPassword(password, hash);
  console.log('Valid:', isValid); // true
  
  // Verify wrong password
  const isInvalid = await verifyPassword('wrongPassword', hash);
  console.log('Invalid:', isInvalid); // false
}

main();

Python

python
import bcrypt

def hash_password(password: str) -> bytes:
    """Hash a password with bcrypt."""
    salt = bcrypt.gensalt(rounds=12)
    hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
    return hashed

def verify_password(password: str, hashed: bytes) -> bool:
    """Verify a password against a hash."""
    return bcrypt.checkpw(password.encode('utf-8'), hashed)

# Usage
password = "mySecurePassword123"

# Hash
hashed = hash_password(password)
print(f"Hash: {hashed.decode()}")

# Verify
is_valid = verify_password(password, hashed)
print(f"Valid: {is_valid}")  # True

is_invalid = verify_password("wrongPassword", hashed)
print(f"Invalid: {is_invalid}")  # False

Java (Spring Security)

java
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class BcryptExample {
    public static void main(String[] args) {
        // Create encoder with strength (cost factor) 12
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);
        
        String password = "mySecurePassword123";
        
        // Hash the password
        String hash = encoder.encode(password);
        System.out.println("Hash: " + hash);
        
        // Verify password
        boolean isValid = encoder.matches(password, hash);
        System.out.println("Valid: " + isValid); // true
        
        boolean isInvalid = encoder.matches("wrongPassword", hash);
        System.out.println("Invalid: " + isInvalid); // false
    }
}

Go

go
package main

import (
    "fmt"
    "golang.org/x/crypto/bcrypt"
)

func hashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
    return string(bytes), err
}

func verifyPassword(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

func main() {
    password := "mySecurePassword123"
    
    // Hash
    hash, _ := hashPassword(password)
    fmt.Println("Hash:", hash)
    
    // Verify
    isValid := verifyPassword(password, hash)
    fmt.Println("Valid:", isValid) // true
    
    isInvalid := verifyPassword("wrongPassword", hash)
    fmt.Println("Invalid:", isInvalid) // false
}

Security Best Practices

Do's ✅

  1. Use cost factor 10-12 for production systems
  2. Store the complete hash string (includes salt and version)
  3. Use constant-time comparison (built into bcrypt libraries)
  4. Upgrade cost factor as hardware improves
  5. Use HTTPS when transmitting passwords
  6. Implement rate limiting on login endpoints

Don'ts ❌

  1. Don't use cost factor below 10 in production
  2. Don't store salt separately (it's in the hash)
  3. Don't truncate passwords before hashing
  4. Don't use bcrypt for non-password data (use SHA-256)
  5. Don't log passwords or hashes in plain text
  6. Don't implement bcrypt yourself (use established libraries)

Password Length Considerations

Bcrypt has a maximum input length of 72 bytes. For longer passwords:

javascript
// Pre-hash long passwords with SHA-256
const crypto = require('crypto');
const bcrypt = require('bcryptjs');

async function hashLongPassword(password) {
  // Pre-hash if password might exceed 72 bytes
  const preHash = crypto
    .createHash('sha256')
    .update(password)
    .digest('base64');
  
  return bcrypt.hash(preHash, 12);
}

FAQ

Q1: Why does the same password produce different hashes?

Bcrypt generates a unique random salt for each hash operation. This salt is embedded in the output string. During verification, the salt is extracted and used to reproduce the hash. This design prevents rainbow table attacks.

Q2: What cost factor should I use?

For most applications, use cost factor 10-12, targeting 100-300ms hash time. Test on your production hardware and adjust accordingly. Higher security applications can use 13-14 if the UX impact is acceptable.

Q3: Is Bcrypt still secure in 2026?

Yes, Bcrypt remains secure and is recommended by OWASP. While Argon2 is preferred for new projects, Bcrypt with cost factor 10+ provides excellent protection against brute-force attacks.

Q4: Can I use Bcrypt for API keys or tokens?

No, Bcrypt is designed for password verification, not general hashing. For API keys or tokens, use SHA-256 or HMAC-SHA256. Bcrypt's slowness would create performance issues for high-frequency operations.

Q5: How do I migrate from MD5 to Bcrypt?

During user login, verify against MD5, then rehash with Bcrypt:

javascript
async function migrateHash(password, oldMd5Hash) {
  const md5 = crypto.createHash('md5').update(password).digest('hex');
  
  if (md5 === oldMd5Hash) {
    // Password correct, create new bcrypt hash
    const newHash = await bcrypt.hash(password, 12);
    await updateUserHash(newHash);
    return true;
  }
  return false;
}

Conclusion

Bcrypt remains one of the most reliable password hashing algorithms available. Its built-in salt generation, configurable cost factor, and battle-tested implementation make it an excellent choice for securing user credentials.

Key points to remember:

  • Always use cost factor 10+ in production
  • The hash string contains everything needed for verification
  • Upgrade cost factor as hardware improves
  • Consider Argon2 for new projects with memory-hard requirements

Ready to generate or verify Bcrypt hashes? Try our free online tool:

Generate Bcrypt Hash Now

For more security tools, check out our Hash Generator and Password Generator.