For years, developers have followed the mantra:
“Don’t store passwords in plain text. Hash them.”
This advice was well-meaning, but in 2025 it’s dangerously outdated.
If you’re still relying on SHA-256, SHA-512, or MD5 for password storage, you’re putting your users at risk.
⸻
The Problem with Simple Hashing
General-purpose hashing algorithms (SHA-256, SHA-512, MD5, etc.) were designed to be fast and efficient. That’s perfect for checksums, digital signatures, or data integrity — but disastrous for password storage.
Why?
• Speed favors attackers: GPUs and ASICs can compute billions of SHA-256 hashes per second.
• Parallelism is cheap: attackers can run millions of password guesses in parallel.
• No built-in protections: general hashes don’t enforce salts, iteration counts, or memory-hardness.
This means that if your database is leaked, a simple hash provides almost no resistance against brute-force cracking.
⸻
Enter Key Derivation Functions (KDFs)
A Key Derivation Function (KDF) is specifically designed for secure password storage. Instead of speed, KDFs focus on defense.
Key properties of KDFs:
• 🔑 Unique Salt — ensures the same password produces different hashes, preventing rainbow table attacks.
• 🕒 Work Factor / Iterations — slows down each password guess, making brute-force expensive.
• 💾 Memory Hardness (Argon2, scrypt) — forces attackers to use significant RAM, reducing the effectiveness of GPUs/ASICs.
With KDFs, every password guess is deliberately slow and resource-intensive, raising the cost of cracking attempts.
⸻
Which KDF Should You Use?
According to the OWASP Password Storage Cheat Sheet (2023) and NIST SP 800-63B Digital Identity Guidelines, recommended algorithms include:
• Argon2id – Winner of the Password Hashing Competition, memory-hard, side-channel resistant.
• bcrypt – Battle-tested and still widely used, though not memory-hard.
• scrypt – Memory-hard, but less widely adopted than Argon2.
• PBKDF2 (with HMAC-SHA256) – Acceptable if configured with very high iterations, but considered weaker than Argon2/bcrypt.
Example Starting Parameters (OWASP guidance)
• Argon2id: memory cost = 64 MB, time cost = 2, parallelism = 1
• bcrypt: cost factor = 12–14
• scrypt: N=2^15, r=8, p=1
• PBKDF2-HMAC-SHA256: 200,000+ iterations
Always benchmark for your environment and set parameters so hashing takes ~100–300 ms on your server.
⸻
Implementation Example (Node.js with Argon2id)
const argon2 = require('argon2');
// Hash a password
const hash = await argon2.hash("UserPassword123", {
type: argon2.argon2id,
timeCost: 2,
memoryCost: 65536, // 64 MB
parallelism: 1,
});
// Verify later
const isValid = await argon2.verify(hash, "UserPassword123");
The stored string includes algorithm, parameters, salt, and hash — making it future-proof.
⸻
Best Practices
1. Never use raw hashes (SHA-256, SHA-512, MD5, etc.) for passwords.
2. Always use a unique cryptographic salt per password.
3. Store algorithm + parameters with the hash (for migration and upgrades).
4. Benchmark and adjust so hashing is slow enough to deter attacks but fast enough for user logins.
5. Rehash periodically when parameters or algorithms improve.
6. Add layers of defense: HTTPS, account lockouts, 2FA, and possibly a server-side “pepper” stored separately.
⸻
Closing Thought
Password security is not just about avoiding plain-text storage — it’s about resisting modern cracking techniques.
• Hashing with SHA-256 is no longer enough.
• KDFs like Argon2id, bcrypt, or scrypt are purpose-built for defense.
• OWASP and NIST explicitly recommend them.
If you’re still hashing passwords, it’s time to move on.
Your users — and their security — deserve better.
⸻
🔗 References:
• OWASP Password Storage Cheat Sheet
• NIST SP 800-63B Digital Identity Guidelines
• Password Hashing Competition
Top comments (0)