On this page
How to Store Passwords Correctly (Hashing vs Encryption)
Passwords must be hashed with a slow algorithm like Argon2 or bcrypt, never encrypted or stored in plaintext. Learn why and how, with code examples.
Quick answer
- Passwords must be hashed, not encrypted: hashing is one-way, so a stolen database doesn’t reveal passwords.
- Use a slow, salted algorithm — Argon2id, bcrypt, or scrypt — never fast hashes like MD5 or SHA-1.
- Encryption is reversible and therefore wrong for passwords; it’s one of the most common AI-assistant mistakes.
Hashing vs encryption
Encryption is two-way: encrypt a value, and you can decrypt it back. That’s correct for data you need to read later, but wrong for passwords — if you can reverse it, so can an attacker with the database. Hashing is one-way: a hash function maps input to a fixed output that can’t be reversed. You store the hash, and at login you hash the attempt and compare. This is why “we encrypt passwords” is always a red flag.
Why the algorithm matters
Fast general-purpose hashes (MD5, SHA-1, even plain SHA-256) are wrong for passwords because attackers can test billions of guesses per second. Password hashes must be slow and salted. A salt is a random value added per user so identical passwords don’t produce identical hashes; slowness makes brute force expensive. Argon2id is the current recommendation, with bcrypt and scrypt as acceptable alternatives.
How to do it
In Node.js with bcrypt:
const bcrypt = require("bcrypt");
// Store: hash the password with a salt and cost factor
const hash = await bcrypt.hash(password, 12);
await db.users.insert({ email, password_hash: hash });
// Verify at login
const ok = await bcrypt.compare(attempt, user.password_hash);In Python with Argon2:
from argon2 import PasswordHasher
ph = PasswordHasher()
hashed = ph.hash(password) # store this
ph.verify(hashed, attempt) # raises on mismatchHow to verify it worked: the stored value looks like a long random string, never contains the password, and compare/verify succeeds only for the correct password.
Where this bites vibecoders
This is a documented, specific failure: AI assistants regularly implement reversible “encryption” for passwords instead of hashing, because “store it securely” can be satisfied syntactically by encryption. The check is simple — if your code can turn a stored value back into a password, it’s wrong. Hash, salt, and slow the algorithm; anything else is a breach waiting to be reported.
Where AI coding assistants get this wrong
- Using reversible encryption (AES) or base64 and calling it “secure.”
- Choosing MD5 or SHA-256 for password hashing.
- Hashing without a per-user salt.
- Comparing passwords with
==after decrypting instead of hashing the attempt.
Checklist
- Hash passwords, never encrypt or store plaintext.
- Use Argon2id, bcrypt, or scrypt with a strong cost factor.
- Salt every password with a unique random value.
- Compare hashes using the library’s constant-time verify function.
- Never log passwords or return them in API responses.
FAQ
What is a salt?
A salt is a unique random value combined with each password before hashing. It ensures two users with the same password get different hashes and defeats precomputed “rainbow table” attacks. Modern libraries add salts automatically.
Why not just use SHA-256?
SHA-256 is designed to be fast, which is the opposite of what passwords need. Attackers use that speed to brute-force billions of guesses. Password algorithms are deliberately slow to make each guess expensive.
Is encryption ever OK for passwords?
No — for stored login credentials, hashing is the correct primitive because it’s one-way. Encryption is appropriate for data you legitimately need to decrypt, like API tokens you must present back, but not for passwords.
Related topics
- What Is the OWASP Top 10?
- How to Review AI-Generated Code Like a Senior Engineer
- What Is a Man-in-the-Middle Attack?