Back to Help Center
DEVELOPER September 11, 2026 · 6 min read

How to Generate a Secure Password Hash (bcrypt, Argon2)

Never use MD5, SHA-1, or plain SHA-256 for passwords. Argon2id is the 2026 recommendation; bcrypt is battle-tested and universally supported.

Password hashing is a specific class of hash function — deliberately slow, memory-hard, salted. General-purpose hashes (MD5, SHA-256) are the wrong tool.

The Rule

Use bcrypt or Argon2id. Never MD5, SHA-1, SHA-256, or SHA-512 alone.

Method 1 — bcrypt (Battle-Tested)

Cost factor 12 in 2026 (~250ms per hash on a modern server):

Python:

import bcrypt
password = b"user-password"
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# Verify:
bcrypt.checkpw(password, hashed)  # True

Node.js:

const bcrypt = require('bcrypt');
const hashed = await bcrypt.hash('user-password', 12);
// Verify:
await bcrypt.compare('user-password', hashed);  // true

Ruby:

require 'bcrypt'
hashed = BCrypt::Password.create('user-password', cost: 12)
BCrypt::Password.new(hashed) == 'user-password'  # true

Output format:

$2b$12$Rn8OqhElVXYs.7g5CD3Ovu8k5UyC.pQxPnr4jZTvC9NcJC/1kHJbG

Method 2 — Argon2id (Modern Recommendation)

Parameters for 2026: memory=64MB (65536 KiB), time=3, parallelism=4.

Python:

from argon2 import PasswordHasher
ph = PasswordHasher(memory_cost=65536, time_cost=3, parallelism=4)
hashed = ph.hash("user-password")
# Verify:
ph.verify(hashed, "user-password")  # True

Node.js:

const argon2 = require('argon2');
const hashed = await argon2.hash('user-password', {
  type: argon2.argon2id,
  memoryCost: 65536,
  timeCost: 3,
  parallelism: 4,
});
// Verify:
await argon2.verify(hashed, 'user-password');  // true

Output format:

$argon2id$v=19$m=65536,t=3,p=4$c2FsdHNhbHRzYWx0$hashhashhashhash...

Method 3 — Quick Testing

Use the hash generator — paste a password, get bcrypt or Argon2 output. Useful for testing verification code without writing generation code.

Cost Tuning

Both bcrypt and Argon2 support tunable work factors. Target ~250ms per hash on your production hardware:

  • bcrypt — cost 12 in 2026, bump to 13 in ~2 years
  • Argon2id — memory=64MB is the safe baseline; bump to 128MB for higher-security systems

Rehash on user login when the cost is out of date.

Salt

Both bcrypt and Argon2 include salt automatically — you don’t need to add it separately. The salt is embedded in the output hash string.

Verification

Never do hash(input) == stored_hash for comparison — use the library’s verify function to prevent timing attacks:

  • bcrypt: bcrypt.checkpw() (Python), bcrypt.compare() (Node)
  • Argon2: PasswordHasher.verify() (Python), argon2.verify() (Node)

Common Miss

  • Using MD5 / SHA-256 / SHA-512 for passwords — modern GPUs crack billions per second
  • Wrapping bcrypt around SHA-256 (bcrypt(sha256(password))) — adds no security, caps entropy at 256 bits
  • Storing hashes without cost/parameters — the hash format includes them; keep the format intact
  • Truncating passwords at 72 bytes without warning users (bcrypt’s limit) — silent security regression
  • Custom “salted SHA-256” schemes — always insecure vs bcrypt/Argon2

Read the bcrypt glossary entry and Argon2 entry for the algorithm details.

Check the bcrypt glossary entry, the Argon2 entry, and see why MD5 is broken for passwords.