Password Hashing (bcrypt, Argon2, PBKDF2)
What is Password Hashing?
Password hashing is the process of converting plaintext passwords into irreversible, fixed-length strings using cryptographic hash functions. Unlike encryption, hashing is a one-way function - it cannot be reversed to obtain the original password. Password hashing is essential for securely storing user credentials and protecting against data breaches.
Why Password Hashing Matters
Data Breach Protection
If a database is compromised, hashed passwords are useless to attackers. Without hashing, attackers gain access to plaintext passwords that can be used for credential stuffing attacks across multiple services.
Compliance Requirements
GDPR, PCI DSS, HIPAA, and other regulations require secure password storage. Hashing is considered best practice and often mandatory for compliance.
Defense Against Rainbow Tables
Attackers use precomputed hash tables (rainbow tables) to crack passwords. Salted hashes make rainbow tables ineffective by ensuring each password has a unique hash.
How Password Hashing Works
- User creates an account with a password
- Server applies a hash function + salt to the password
- Only the hash and salt are stored in the database
- During authentication, the server hashes the provided password and compares it to the stored hash
Password Hashing Algorithms
bcrypt
- Designed specifically for password hashing
- Built-in salt generation and storage
- Adaptive work factor that can be increased over time
- Example (Node.js):
const bcrypt = require('bcrypt'); const saltRounds = 12; // Hashing bcrypt.hash('password123', saltRounds, (err, hash) => { // Store hash in database }); // Verification bcrypt.compare('password123', storedHash, (err, result) => { if (result) console.log('Password matches!'); });
Argon2
- Winner of the Password Hashing Competition (2015)
- Memory-hard function resistant to GPU and ASIC attacks
- Three variants: Argon2d, Argon2i, Argon2id (recommended)
- Example (Node.js):
const argon2 = require('argon2'); argon2.hash('password123', { type: argon2.argon2id, memoryCost: 65536, // 64MB timeCost: 3 }).then(hash => { // Store hash in database });
PBKDF2
- NIST-approved standard (RFC 8018)
- Configurable iteration count to increase computation time
- Uses HMAC with a pseudorandom function like SHA-256
- Example (Node.js):
const crypto = require('crypto'); const salt = crypto.randomBytes(16).toString('hex'); const iterations = 100000; const keylen = 64; const digest = 'sha512'; crypto.pbkdf2('password123', salt, iterations, keylen, digest, (err, derivedKey) => { const hash = derivedKey.toString('hex'); // Store hash + salt in database });
Comparison of Algorithms
| Feature | bcrypt | Argon2 | PBKDF2 |
|---|---|---|---|
| Security | High | Very High | High |
| Speed | Slow | Very Slow | Configurable |
| Memory Usage | Low | High | Low |
| GPU Resistance | Good | Excellent | Good |
| Built-in Salt | Yes | Yes | No |
Best Practices
- Use modern algorithms: Prefer Argon2id or bcrypt over older algorithms
- Use high work factors: bcrypt (12+ rounds), Argon2 (64MB+ memory), PBKDF2 (100,000+ iterations)
- Always use a salt: Unique salt for each password (16+ bytes)
- Implement secure storage: Store only hashes and salts, never plaintext
- Use secure libraries: Avoid custom implementations
- Implement rate limiting: Prevent brute force attacks
- Plan for upgrades: Monitor algorithm vulnerabilities and performance
Common Attacks and Mitigations
Rainbow Table Attacks
- Vulnerability: Precomputed hash tables for common passwords
- Mitigation: Use unique salts for each password
Brute Force Attacks
- Vulnerability: Trying all possible password combinations
- Mitigation: Use slow hash functions with high work factors
GPU/ASIC Attacks
- Vulnerability: Using specialized hardware to crack hashes
- Mitigation: Use memory-hard algorithms like Argon2
