Password Hashing
Regular hash functions (SHA-256, etc.) are NOT suitable for passwords. Here's why and what to use instead.
Why SHA-256 is Wrong for Passwords
// ❌ WRONG: Raw SHA-256
const hash = crypto.createHash('sha256')
.update(password)
.digest('hex');
// Problems:
// 1. Too fast - attackers can try billions/second
// 2. No salt - rainbow table attacks
// 3. Identical passwords have identical hashes
The Right Way: bcrypt
import bcrypt from 'bcrypt';
// Hashing a password
const saltRounds = 12;
const hash = await bcrypt.hash(password, saltRounds);
// $2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.aAfnvKY5P8m3K.
// Verifying a password
const isMatch = await bcrypt.compare(password, hash);
Even Better: Argon2
import argon2 from 'argon2';
// Hashing
const hash = await argon2.hash(password, {
type: argon2.argon2id, // Recommended variant
memoryCost: 65536, // 64 MB
timeCost: 3, // Iterations
parallelism: 4 // Threads
});
// Verifying
const isMatch = await argon2.verify(hash, password);
Comparison
| Feature | SHA-256 | bcrypt | Argon2 |
|---|
| Purpose | General | Passwords | Passwords |
|---|
| Salt | Manual | Built-in | Built-in |
|---|
Best Practices
- Never store plaintext passwords
- Use Argon2id or bcrypt
- Use a work factor that takes ~250ms
- Increase work factor over time
- Re-hash on login when upgrading
- Implement rate limiting
- Use secure password requirements