Password Security: Hashing, Salting & Bcrypt vs Argon2 Guide
Password Security: Hashing & Salting — Bcrypt vs Argon2 (2025)
Passwords are still the #1 authentication method. LinkedIn (6M passwords leaked), Yahoo (3B accounts), Equifax (147M identities) — inadequate hashing played a major role in each disaster.
This guide teaches proper password storage that stops attackers cold.
Why Password Hashing Matters
Plaintext passwords stolen? Instant full access to every user account.
Fast hashes (MD5, SHA1)? Modern GPUs crack billions per second using rainbow tables.
Unsalted hashes? Attacker uses pre-computed tables and bypasses your hash entirely.
Weak hashing algorithm? All your stored passwords are effectively encrypted with a weak cipher.
Algorithm Comparison (2025)
| Algorithm | Speed | Memory | GPU-Resistant | Recommended | Best For |
|---|---|---|---|---|---|
| Plaintext | ⚡⚡⚡ Instant | None | ❌ No | ❌ Never | — |
| MD5/SHA1 | ⚡⚡ Fast | None | ❌ No | ❌ Never | — |
| SHA256 | ⚡ Fast | None | ❌ No | ❌ Avoid | — |
| PBKDF2 | 🚷 Slow | Low | ⚠️ Medium | ✅ FIPS systems | Legacy compliance |
| bcrypt | 🚷 Slow | Low | ✅ Good | ✅ Yes | Most applications |
| scrypt | 🚷🚷 Slower | High | ✅✅ Excellent | ✅ Yes | High-security apps |
| Argon2id | 🚷🚷 Slower | High | ✅✅ Excellent | ✅✅ Best | New projects (NIST) |
Bcrypt: The Battle-Tested Choice
Bcrypt was designed in 1999 and is still safe TODAY because it intentionally uses exponential slowdown (cost factor). The slower hashing is a feature, not a bug.
How Bcrypt Works
- Generate a random salt from 2^cost rounds of permutation
- Run password + salt through Blowfish cipher cost times
- Store the salt + cost + hash together
Example:
- Input password: "hunter2"
- Cost: 12 (2^12 = 4,096 iterations)
- Result:
$2b$12$aWjd2hgh3vWs4Ejz8jK47DpO3U5/s8Zi5hj5H4M7HpI... - Time to hash: ~250ms (intentionally slow)
Node.js Bcrypt Implementation
Install:
npm install bcryptjs
Hash password:
const bcrypt = require('bcryptjs');
async function registerUser(email, password) {
const salt = await bcrypt.genSalt(12); // Cost factor 12
const hashedPassword = await bcrypt.hash(password, salt);
// Store hashedPassword in database
await User.create({ email, passwordHash: hashedPassword });
}
Verify password:
async function loginUser(email, passwordAttempt) {
const user = await User.findOne({ email });
const isValid = await bcrypt.compare(passwordAttempt, user.passwordHash);
if (isValid) {
// Authentication successful
return createSession(user);
} else {
// Wrong password
return 'Invalid credentials';
}
}
Cost factor timing:
- Cost 10: 10ms (old hardware)
- Cost 12: 250ms (standard, 2024)
- Cost 14: 1 second (future-proofing)
- Cost 16: 2 seconds (admin accounts only)
Argon2: The Modern Standard
Argon2 won the Password Hashing Competition (2015) and is now recommended by NIST. It's memory-hard, making GPU/ASIC attacks infeasible.
Node.js Argon2 Implementation
Install:
npm install argon2
Hash:
const argon2 = require('argon2');
async function registerUserArgon2(email, password) {
const hash = await argon2.hash(password, {
type: argon2.argon2id, // Recommended
memoryCost: 65536, // 64 MB
timeCost: 3, // 3 iterations
parallelism: 4 // 4 parallel threads
});
await User.create({ email, passwordHash: hash });
}
Verify:
async function loginUserArgon2(email, passwordAttempt) {
const user = await User.findOne({ email });
const isValid = await argon2.verify(user.passwordHash, passwordAttempt);
if (isValid) {
return createSession(user);
} else {
return 'Invalid credentials';
}
}
Python Argon2
pip install argon2-cffi
from argon2 import PasswordHasher
hasher = PasswordHasher()
hash = hasher.hash('hunter2')
hasher.verify(hash, 'hunter2') # True
Beyond Hashing: Defense in Depth
Salts & Peppers:
- Salt: Random per-password, stored with hash (bcrypt/argon2 handle this)
- Pepper: Server secret appended before hashing (stored in env, NOT in code)
Password Rotation:
- Re-hash on next login when you upgrade algorithms
- Support graceful migration from old to new algorithms
Additional Protections:
- Block common passwords (rockyou.txt, have-i-been-pwned lists)
- Enforce minimum length (12+ characters)
- Rate limit login attempts (exponential backoff)
- Monitor for credential stuffing (impossible logins from new IPs)
- Require MFA (passwords alone insufficient)
Migration Plan: Upgrading Hash Algorithms
If you're using MD5/SHA1:
// On login, check if password matches old hash
if (md5(password) === user.oldMd5Hash) {
// Authentication passed! Re-hash with bcrypt
user.bcryptHash = await bcrypt.hash(password, 12);
user.oldMd5Hash = null;
await user.save();
}
Password Security Checklist
- Use Argon2id (new projects) or bcrypt (existing)
- Cost/time parameters tuned to 250-500ms per hash
- Never use MD5, SHA1, or unsalted hashes
- Block common passwords (NIST list)
- Enforce minimum 12-character passwords
- Rate limit login endpoints (exponential backoff)
- Require MFA for sensitive operations
- Monitor for credential stuffing
- Support password rotation/upgrade on next login
Resources
- https://owasp.org/www-community/attacks/Password_Cracking
- https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
- https://pages.nist.gov/800-63-3/sp800-63b.html (NIST password guidelines)
- https://libsodium.gitbook.io/doc (libsodium for additional crypto)
Next Step: Audit your password storage now. If using MD5/SHA1, implement bcrypt upgrade on next login.
Advertisement
Free Security Tools
Try our tools now
Expert Services
Get professional help
OWASP Top 10
Learn the top risks
Related Articles
DevSecOps: The Complete Guide 2025-2026
Master DevSecOps with comprehensive practices, automation strategies, real-world examples, and the latest trends shaping secure development in 2025.
The Ultimate Secure Code Review Checklist for 2025
A comprehensive, actionable checklist for conducting secure code reviews. Covers input validation, authentication, authorization, cryptography, error handling, and CI/CD integration with real-world examples.
JWT Security: Vulnerabilities, Best Practices & Implementation Guide
Comprehensive JWT security guide covering token anatomy, common vulnerabilities, RS256 vs HS256, refresh tokens, and secure implementation patterns.