How to Store Passwords Securely: bcrypt, Argon2, and scrypt Explained

How to Store Passwords Securely: bcrypt, Argon2, and scrypt Explained

Try the Hash Generator

How to Store Passwords Securely: bcrypt, Argon2, and scrypt Explained

Meta Description: Learn how to hash passwords securely with bcrypt, Argon2, and scrypt. Understand why MD5 and SHA are wrong for passwords and implement secure storage.

Target Keywords: bcrypt generator, password hashing, bcrypt vs argon2, secure password storage, scrypt


In 2024, a major company lost 150 million password hashes in a breach. Because they used SHA-1, attackers cracked 90% of them within 72 hours.

That same breach, with bcrypt, would have yielded maybe 0.1% of passwords—and taken years.

The algorithm matters. Here's how to choose the right one.

Why MD5 and SHA Are Wrong for Passwords

"But SHA-256 is secure!" Yes, for file integrity. Not for passwords. Here's why:

The Speed Problem

Algorithm Hashes/Second (GPU) Time to Crack 8-char Password
MD5 ~180 billion < 1 minute
SHA-256 ~10 billion ~30 minutes
bcrypt (cost 12) ~30,000 ~300 years

SHA-256 is too fast. Attackers can try billions of guesses per second. Password hashing algorithms are intentionally slow.

The Rainbow Table Problem

Precomputed tables map common passwords to hashes:

password123 → 482c811da5d5b4bc6d497ffa98491e38 (MD5)

Without salting, one table cracks millions of accounts. Password hashes require unique salts per password.

The Right Way: Purpose-Built Algorithms

Password hashing algorithms are designed with:

  • Slowness: Configurable work factor makes hashing slow
  • Built-in salt: Each password gets unique random salt
  • Memory hardness: Some require significant RAM, defeating GPU attacks
  • Resistance: Can't be sped up with custom hardware

bcrypt: The Battle-Tested Standard

Released: 1999 Based on: Blowfish cipher Status: ✅ Secure, widely deployed

How bcrypt Works

  1. Generate random 16-byte salt
  2. Use password + salt as Blowfish key
  3. Encrypt magic string "OrpheanBeholderScryDoubt" 64 times
  4. Output: algorithm + cost + salt + hash

bcrypt Output Format

$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/..0J4mMLdHn.J3Hqy
│ │  │                                      │
│ │  │                                      └── Hash (31 chars)
│ │  └── Salt (22 chars)
│ └── Cost factor (4-31, higher = slower)
└── Algorithm version (2b = current)

bcrypt in Practice

Node.js:

import bcrypt from 'bcrypt';

// Hash password
const saltRounds = 12;
const hash = await bcrypt.hash('user-password', saltRounds);

// Verify password
const match = await bcrypt.compare('user-password', hash);

Python:

import bcrypt

# Hash password
password = b'user-password'
salt = bcrypt.gensalt(rounds=12)
hash = bcrypt.hashpw(password, salt)

# Verify password
if bcrypt.checkpw(password, hash):
    print("Password correct!")

Choosing bcrypt Cost Factor

Cost Hashes/sec (single core) Recommended For
10 ~10 Development/testing
12 ~2-3 Web applications
14 ~0.5 High security
16+ <0.1 Ultra-sensitive

Rule of thumb: Hash should take 250ms-500ms on your server.

bcrypt Limitations

  • 72-byte password limit: Passwords longer than 72 bytes are truncated
  • CPU-bound only: Doesn't use memory, vulnerable to GPU attacks (though still slow)
  • Older design: Newer algorithms have additional protections

scrypt: Memory-Hard Protection

Released: 2009 Status: ✅ Secure, used in cryptocurrency

How scrypt Works

scrypt requires significant memory to compute, defeating GPU and ASIC attacks:

  1. Generate large memory buffer from password + salt
  2. Mix buffer contents repeatedly
  3. Output hash from final buffer state

GPUs have many cores but limited memory per core. scrypt turns this weakness against attackers.

scrypt Parameters

scrypt(password, salt, N, r, p, keyLength)
Parameter Meaning Typical Value
N CPU/memory cost (power of 2) 16384 (2^14)
r Block size 8
p Parallelization 1
keyLength Output length 32

scrypt in Practice

Node.js:

import { scrypt, randomBytes } from 'crypto';
import { promisify } from 'util';

const scryptAsync = promisify(scrypt);

// Hash password
const salt = randomBytes(16);
const hash = await scryptAsync('user-password', salt, 64, {
  N: 16384,
  r: 8,
  p: 1
});

Python:

import hashlib

# Hash password
hash = hashlib.scrypt(
    b'user-password',
    salt=os.urandom(16),
    n=16384,
    r=8,
    p=1,
    dklen=64
)

scrypt Use Cases

  • Cryptocurrency wallets (Litecoin, Dogecoin)
  • Disk encryption
  • High-security password storage

Argon2: The Modern Standard

Released: 2015 Won: Password Hashing Competition Status: ✅ Recommended for new projects

Argon2 Variants

Variant Best For Protection Against
Argon2d Cryptocurrency GPU/ASIC attacks
Argon2i Password hashing Side-channel attacks
Argon2id General use (recommended) Both attack types

Use Argon2id for password hashing. It combines the strengths of both variants.

Argon2 Parameters

argon2id(password, salt, memory, iterations, parallelism, hashLength)
Parameter Meaning OWASP Recommendation
memory Memory cost (KB) 46 MB (46080 KB)
iterations Time cost 1
parallelism Threads 1
hashLength Output length 32 bytes

Argon2 in Practice

Node.js:

import argon2 from 'argon2';

// Hash password
const hash = await argon2.hash('user-password', {
  type: argon2.argon2id,
  memoryCost: 46080,  // 46 MB
  timeCost: 1,
  parallelism: 1
});

// Verify password
const valid = await argon2.verify(hash, 'user-password');

Python:

from argon2 import PasswordHasher

ph = PasswordHasher(
    memory_cost=46080,  # 46 MB
    time_cost=1,
    parallelism=1
)

# Hash password
hash = ph.hash('user-password')

# Verify password
try:
    ph.verify(hash, 'user-password')
    print("Password correct!")
except:
    print("Invalid password")

Argon2 Output Format

$argon2id$v=19$m=46080,t=1,p=1$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG
│        │   │              │            │
│        │   │              └── Salt     └── Hash
│        │   └── Parameters (memory, time, parallelism)
│        └── Version
└── Algorithm

Comparison Table

Feature bcrypt scrypt Argon2id
Released 1999 2009 2015
Memory-hard
GPU resistant ⚠️ Moderate ✅ Strong ✅ Strong
ASIC resistant
Password limit 72 bytes None None
Side-channel resistant ✅ (Argon2id)
Ecosystem Excellent Good Growing
OWASP recommended ✅ Yes ✅ Yes ✅ Primary

Which Should You Use?

Use Argon2id When:

  • ✅ Starting a new project
  • ✅ Can use recent libraries
  • ✅ Want best available security
  • ✅ OWASP/NIST compliance matters

Use bcrypt When:

  • ✅ Maintaining existing systems using bcrypt
  • ✅ Language/platform lacks good Argon2 support
  • ✅ Need battle-tested, widely audited algorithm
  • ✅ Password limit of 72 bytes is acceptable

Use scrypt When:

  • ✅ Already using scrypt in your stack
  • ✅ Need memory-hard without Argon2 support
  • ✅ Cryptocurrency-related applications

Implementation Checklist

Never store plain-text passwords

Use bcrypt, scrypt, or Argon2id—never MD5/SHA for passwords

Don't implement crypto yourself—use established libraries

Use sufficient work factors (bcrypt cost 12+, Argon2 46MB+ memory)

Upgrade work factors over time as hardware gets faster

Hash on server side, never client side (prevents hash-the-hash attacks)

Rate limit login attempts (prevent online brute force)

Generate Password Hashes

Test password hashing with our tools:


FAQ

Can I migrate from MD5 to bcrypt without resetting all passwords?

Yes, using lazy migration:

  1. Keep old MD5 hashes
  2. On login, verify with MD5
  3. If valid, hash with bcrypt and update database
  4. Over time, most active users migrate automatically

What cost factor should I use for bcrypt?

Start with 12 and measure. Aim for 250-500ms hash time on your production server. Increase as hardware improves.

Is Argon2 supported in my language?

Yes, for most languages: JavaScript (argon2 npm), Python (argon2-cffi), Go (golang.org/x/crypto/argon2), PHP (password_hash), Java (Bouncy Castle), Ruby (argon2 gem).

Should I pepper passwords too?

A pepper is a secret added to all passwords before hashing. It adds defense-in-depth but complicates key rotation. Use if your threat model requires it.

How do I increase bcrypt cost for existing users?

On successful login, check if hash uses old cost, re-hash with new cost, and update database. Same lazy migration as algorithm changes.


Related Tools:

Generate Hashes Instantly

Create MD5, SHA-256, SHA-512, bcrypt, and more — 100% client-side, your data never leaves your browser.

Open Hash Generator