What Is Password Hashing? bcrypt Explained for AI Coders

When you ask AI to build authentication, it stores passwords as hashes, not plain text. This is one of the most important security concepts in web development — here's what it means and why it matters.

TL;DR

Never store passwords as plain text. Instead, hash them — a one-way transformation that can't be reversed. bcrypt is the industry-standard algorithm for this. When a user logs in, you hash their entered password and compare it to the stored hash. If they match, the password is correct. AI does this automatically when building auth — here's what it generates and why each piece matters.

Why AI Coders Must Understand Password Hashing

Every year, millions of user accounts are compromised in database breaches. The difference between a breach that destroys a company and one that's quickly contained is often a single decision made years earlier: did you store passwords properly?

When AI builds authentication for you, it uses bcrypt to hash passwords. But if you don't understand why, you might:

  • Replace AI's hashing code with something simpler that's dangerously insecure
  • Use MD5 or SHA-256 (which you've heard of) instead of bcrypt (which is correct)
  • Store passwords encrypted instead of hashed (wrong — encryption can be reversed)
  • Skip the cost factor or set it too low, making your hashes brute-forceable

This is a case where understanding AI's output is critical to not shipping a security disaster.

Real Scenario: User Registration and Login

Your AI Prompt

"Build user registration and login for my Node.js/Express app with PostgreSQL. Store passwords securely."

What AI Generated

// auth.js
// Tested with bcryptjs 2.4.x, Node.js 22.x

const bcrypt = require('bcryptjs')

// REGISTRATION: Hash the password before storing it
async function registerUser(email, password) {
  // Cost factor 12: intentionally slow to resist brute-force attacks
  // Higher = slower to hash AND slower to crack. 12 is the 2026 recommendation.
  const saltRounds = 12

  // bcrypt.hash() automatically generates a random salt and applies it
  // The hash output looks like: $2b$12$LKrKlf7XMlUq0Z.OJqUlLu.HNvHMR4VH9qDm3BoFtGmxZ4tSjzMfq
  const hashedPassword = await bcrypt.hash(password, saltRounds)

  // Store hashedPassword in database — NEVER store the plain-text password
  const user = await db.user.create({
    data: {
      email,
      password: hashedPassword  // This is what goes in the database
    }
  })

  return user
}

// LOGIN: Compare entered password against stored hash
async function loginUser(email, enteredPassword) {
  // Get the user (and their hashed password) from database
  const user = await db.user.findUnique({ where: { email } })

  if (!user) {
    throw new Error('Invalid credentials')  // Don't say "user not found" — security
  }

  // bcrypt.compare() hashes the entered password with the SAME salt
  // that was used when the hash was created, then compares
  const passwordMatches = await bcrypt.compare(enteredPassword, user.password)

  if (!passwordMatches) {
    throw new Error('Invalid credentials')  // Same generic error — security
  }

  return user  // Login successful
}

Understanding How Hashing Works

One-Way Transformation

A hash function takes input of any length and produces a fixed-length output. The same input always produces the same output. But you cannot go backwards — given the hash, you cannot recover the original password.

// Same input → same hash (deterministic)
bcrypt.hash("myPassword123", 12) → "$2b$12$LKrKlf..."
bcrypt.hash("myPassword123", 12) → "$2b$12$DIFFERENT..."  // Different! (random salt)

// Wait — different hash for same password? Yes. That's the salt.

The Salt: Why Hashes Differ for the Same Password

A salt is random data added to the password before hashing. bcrypt generates a new random salt for every hash. This means:

  • Two users with the same password have different hashes in the database
  • Pre-computed attack tables (rainbow tables) become useless
  • Attackers must crack each password individually

The salt is stored inside the hash string itself (bcrypt embeds it). When you call bcrypt.compare(), it extracts the salt from the stored hash and re-hashes the entered password with that same salt for comparison.

Reading a bcrypt Hash

$2b$12$LKrKlf7XMlUq0Z.OJqUlLu.HNvHMR4VH9qDm3BoFtGmxZ4tSjzMfq
│  │  │ └─────────────────────────────────────────────────────────┘
│  │  │   The hash (22 chars salt + 31 chars hash, base64 encoded)
│  │  │
│  │  └── Cost factor (12 = 2^12 = 4,096 iterations)
│  │
│  └───── Version (2b = current bcrypt version)
│
└──────── Algorithm identifier

Why Not MD5 or SHA-256?

MD5 and SHA-256 are designed to be fast — they can hash billions of values per second on modern hardware. That's great for checksums and data integrity. It's catastrophic for passwords.

An attacker who steals a database of SHA-256 password hashes can run a brute-force attack testing billions of common passwords per second until they find matches.

bcrypt is deliberately slow. At cost factor 12, it performs ~4,000 iterations, taking ~250ms per hash. The math:

  • SHA-256: 10 billion hashes/second → cracks "password123" instantly
  • bcrypt (cost 12): ~4 hashes/second → would take 80 years to test 1 billion passwords

Slowness is a feature, not a bug. Use bcrypt (or Argon2) for passwords. Never use MD5 or SHA-256 for passwords.

Hashing vs. Encryption

HashingEncryption
Reversible?No — one-wayYes — with the key
Use for passwords?✅ Yes❌ No
Why?Can't be reversed even if DB stolenKey theft → all passwords exposed
Examplesbcrypt, Argon2, SHA-256AES-256, RSA

What AI Gets Wrong About Password Hashing

1. Using bcrypt Synchronously (Blocks the Server)

// WRONG: bcrypt.hashSync blocks Node.js event loop during hashing
const hash = bcrypt.hashSync(password, 12)  // App freezes for 250ms per call

// CORRECT: Always use async version
const hash = await bcrypt.hash(password, 12)  // Non-blocking

2. Cost Factor Too Low

AI sometimes generates cost factor 10 or even 8 for "performance." As of 2026, 12 is the recommended minimum. Cost 10 was the 2012 recommendation — hardware is ~1,000x faster now.

3. Comparing Passwords Directly

// WRONG: Don't compare plain text to hash like this
if (enteredPassword === user.password) { ... }  // Always false + insecure

// WRONG: Don't re-hash and compare strings
if (bcrypt.hash(enteredPassword, 12) === user.password) { ... }  // Different salt each time!

// CORRECT: Use bcrypt.compare()
const matches = await bcrypt.compare(enteredPassword, user.password)  // Handles salt extraction

4. Same Error Message Not Used for Both Failures

AI sometimes says "User not found" vs "Wrong password" — different errors tell attackers which emails exist in your system (user enumeration). Always return the same message: "Invalid credentials."

What to Learn Next

Frequently Asked Questions

What is password hashing?

Password hashing transforms a plain-text password into a fixed-length string (the hash) using a one-way mathematical function. You can't reverse a hash back to the original password. When a user logs in, you hash the entered password and compare it to the stored hash — if they match, the password is correct. You never store plain-text passwords.

What is bcrypt?

bcrypt is a password hashing algorithm designed specifically for passwords (unlike MD5 or SHA-256, which are fast general-purpose hashes). bcrypt is intentionally slow and includes a salt automatically, making it resistant to brute-force attacks. It's the industry standard for password storage and what AI generates when you build authentication.

What is the difference between hashing and encryption?

Encryption is two-way: encrypt data with a key, decrypt it back with the key. Hashing is one-way: hash a password, and you can never recover the original. Passwords must be hashed (not encrypted) because if your database is stolen, there's no key that can reveal stored passwords. Encrypted passwords are only as secure as your encryption key.

What is a salt in password hashing?

A salt is random data added to a password before hashing, making each hash unique even if two users share the same password. Without salts, attackers can precompute hashes for millions of common passwords (rainbow tables) and look them up instantly. bcrypt handles salting automatically — it generates and embeds a random salt in every hash.

What bcrypt cost factor should I use?

The recommendation as of 2026 is 12 for most applications. At cost 12, hashing takes ~250ms — fast enough for user experience, slow enough to make brute-force attacks impractical. Cost 10 is acceptable for very high-traffic scenarios where 250ms login latency is a real problem. Never go below 10 for production.