Secure Password Hashing: How to Store Passwords Right
OverMCP Team
TL;DR: To securely store passwords, never use plain text or outdated hashes like MD5/SHA-1. Use a modern, slow, salted hashing algorithm such as Argon2id (preferred) or bcrypt with a cost factor of at least 10. Always hash on the server, use a unique random salt per password, and never roll your own crypto.
Why Secure Password Hashing Matters
Password breaches are the most common cause of account takeovers. If an attacker gains access to your database, they will immediately try to extract passwords. Without proper secure password hashing, they can easily reverse or crack the hashes, compromising every user account. Even if you use other security measures like encryption at rest, the database itself can be exfiltrated via SQL injection, backup leaks, or insider threats. Hashing is your last line of defense.
What Makes a Hashing Algorithm "Secure"?
A secure password hashing algorithm must be:
The following algorithms meet these criteria:
| Algorithm | Strength | Memory-hard | Recommended Use |
|-----------|----------|-------------|-----------------|
| Argon2id (v1.3+) | Very strong | Yes | Best choice for new applications |
| bcrypt | Strong | No (but CPU-intensive) | Good, widely supported |
| scrypt | Strong | Yes | Good, but less common than bcrypt |
| PBKDF2 | Weak | No (unless using HMAC-SHA256 with high iterations) | Legacy only, avoid if possible |
Never use: MD5, SHA-1, SHA-256 (fast hashes), or any custom algorithm.
How to Hash Passwords: Step-by-Step
Using Argon2id (Recommended)
Argon2id is the winner of the Password Hashing Competition and the most secure option. Here's how to use it in Python with the argon2-cffi library:
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # number of iterations (default 3)
memory_cost=65536, # 64 MB memory usage
parallelism=4, # number of threads (default 4)
hash_len=32, # length of the hash (32 bytes = 256 bits)
salt_len=16 # random salt length (16 bytes = 128 bits)
)
# Hashing
hash = ph.hash("my_secret_password")
print(hash) # $argon2id$v=19$m=65536,t=3,p=4$...
# Verification
try:
ph.verify(hash, "my_secret_password")
print("Password matches")
except:
print("Password incorrect")Important: Argon2id automatically generates a unique salt per password and embeds it in the output string. The verify function extracts the salt and parameters from the stored hash.
Using bcrypt (Fallback)
If Argon2id is not available (e.g., older environments), bcrypt is a solid alternative. Use a cost factor of 10 (2^10 = 1024 iterations) or higher:
import bcrypt
# Generate salt (automatically includes cost factor)
salt = bcrypt.gensalt(rounds=12) # cost factor 12
hashed = bcrypt.hashpw(b"my_secret_password", salt)
# Verification
if bcrypt.checkpw(b"my_secret_password", hashed):
print("Password matches")Cost factor: Start with 10 and increase until hash computation takes ~0.5-1 second on your server. This balances security and user experience.
Using Node.js (bcrypt)
const bcrypt = require('bcrypt');
const saltRounds = 12;
// Hashing
const hash = await bcrypt.hash('my_secret_password', saltRounds);
// Verification
const match = await bcrypt.compare('my_secret_password', hash);
if (match) {
console.log('Password matches');
}Common Mistakes to Avoid
How OverMCP Helps
When you're building apps quickly with AI coding tools, it's easy to inadvertently use weak hashing or expose passwords. OverMCP automatically scans your code for insecure password storage patterns, such as missing salts, low bcrypt rounds, or even plaintext passwords. It integrates into your CI/CD pipeline and alerts you before deployment. Run a free scan at overmcp.com to catch these issues early.
Future-Proofing Your Password Storage
FAQ
What is the best algorithm for secure password hashing?
Argon2id is the current gold standard. It is memory-hard, resistant to GPU/ASIC attacks, and has no known vulnerabilities. Use it whenever possible.
How do I store the salt and hash in the database?
Modern libraries like argon2-cffi and bcrypt encode the salt, algorithm parameters, and hash into a single string (e.g., $argon2id$v=19$m=65536,t=3,p=4$<salt>$<hash>). Store this string directly in a VARCHAR(255) column. No separate salt column is needed.
Should I use a pepper (secret salt) in addition to the salt?
A pepper (a secret stored outside the database, e.g., in an environment variable) adds an extra layer of security if the database is compromised but the pepper is not. However, it is not a replacement for proper hashing. Use it only if you have a secure key management system; otherwise, it may give a false sense of security.