DevToolsForYou
Auth & Security

How to Hash a Password Correctly

A practical guide to storing passwords securely — why plain hashing is wrong, which algorithms to use, how salting works, and what a safe implementation looks like.

2 min readUpdated Apr 11, 2026

Why you cannot use a plain hash

MD5, SHA-256, and similar cryptographic hash functions are designed to be fast — that is the problem. A modern GPU can compute billions of SHA-256 hashes per second, making it trivial to brute-force a leaked password database with a dictionary attack. A password hash function must be intentionally slow.

The right algorithms: bcrypt, Argon2, and scrypt

These three algorithms were designed specifically for password hashing. They are slow by design and include a built-in salt. Argon2id is the current recommendation from OWASP and the Password Hashing Competition. bcrypt is a safe, well-supported fallback with decades of production use. scrypt is memory-hard and a good alternative. Never use PBKDF2 with a low iteration count — it can be run on GPUs efficiently.

What a salt does

A salt is a random value generated for each password before hashing. It ensures two users with the same password get different hashes, and it makes precomputed rainbow-table attacks useless. All three recommended algorithms generate and store the salt automatically — you do not manage it yourself.

text
# bcrypt output format — the salt is embedded
$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW
#    ^^ cost factor (12 = 2^12 = 4096 iterations)
#      ^^^^^^^^^^^^^^^^^^^^^^ 22-char salt (built-in)
#                            ^^^^^^^^^^^^^^^^^^^^^^^^ hash

Choosing a work factor

The work factor (cost, iterations, memory) controls how slow the hash is. You want it slow enough that an attacker's brute-force is impractical, but fast enough that your login endpoint responds in under a second. OWASP recommends bcrypt cost 10–12, Argon2id with 64MB memory and 3 iterations, or scrypt with N=32768. Re-benchmark as hardware gets faster — bump the factor every few years.

bcrypt in Node.js

javascript
import bcrypt from 'bcrypt';

const COST_FACTOR = 12;

// Hashing a new password
async function hashPassword(plaintext) {
  return bcrypt.hash(plaintext, COST_FACTOR);
}

// Verifying at login
async function verifyPassword(plaintext, storedHash) {
  return bcrypt.compare(plaintext, storedHash);
}

// Usage
const hash = await hashPassword('hunter2');
// '$2b$12$...'

const ok = await verifyPassword('hunter2', hash);
// true

Argon2id in Node.js

javascript
import argon2 from 'argon2';

// Hashing
async function hashPassword(plaintext) {
  return argon2.hash(plaintext, {
    type: argon2.argon2id,
    memoryCost: 65536,  // 64 MB
    timeCost: 3,
    parallelism: 1,
  });
}

// Verifying
async function verifyPassword(plaintext, storedHash) {
  return argon2.verify(storedHash, plaintext);
}

What to store in the database

Store only the hash string returned by the library — nothing else. bcrypt and Argon2 encode the algorithm, version, cost factor, and salt into the single output string. You do not need separate columns for salt or algorithm. Never store the plaintext password, never log it, and never send it over a network after the initial form submission.

Frequently asked questions

Can I use SHA-256 with a salt for passwords?

No. Even with a per-user salt, SHA-256 is too fast. An attacker with a GPU can still try millions of salted guesses per second per account. Use bcrypt, Argon2id, or scrypt.

What if I inherited a system using MD5 or SHA-1 hashes?

Migrate on next login: when a user successfully logs in with their old hash, re-hash their plaintext password with bcrypt/Argon2 and replace the stored hash. After a while, force a password reset for accounts that have never migrated.

How do I handle pepper in addition to salt?

A pepper is a secret value stored outside the database (in an environment variable or secrets manager) and appended to the password before hashing. It means a leaked database alone is not enough to crack passwords. Implement it as: hash(password + pepper). Rotate the pepper by re-hashing on next login.

Related cheatsheetsAll cheatsheets →
Related guidesAll guides →