bcrypt, Argon2 and Password Policies
Passwords must never be stored as plain text or reversible encryption. Instead, they are passed through a password hashing function — a deliberately slow, one-way function designed to make brute-force attacks computationally expensive. The two most widely used algorithms in Node.js are bcrypt and Argon2. Understanding their differences, configuring their cost parameters correctly, and enforcing a sensible password policy are foundational security responsibilities for every backend developer.
bcrypt: The Industry Workhorse
bcrypt was designed in 1999 specifically for password hashing. It incorporates a salt (random bytes mixed into the input) to prevent rainbow table attacks, and a cost factor (work factor) that controls how many iterations of the hashing algorithm are performed. Doubling the cost factor doubles the computation time for both legitimate logins and attackers. The recommended cost factor in 2024 is 12–14, targeting approximately 250–500 ms per hash on a modern server.
npm install bcryptconst bcrypt = require('bcrypt');
const SALT_ROUNDS = 12; // adjust so hashing takes ~250-300ms on your hardware
// Hashing a password at registration
async function hashPassword(plaintext) {
return bcrypt.hash(plaintext, SALT_ROUNDS);
// bcrypt.hash automatically generates a salt and embeds it in the output
// Output format: $2b$12$<22-char-salt><31-char-hash>
}
// Verifying at login
async function verifyPassword(plaintext, storedHash) {
return bcrypt.compare(plaintext, storedHash);
// Returns true/false; timing-safe comparison built in
}
// Usage
const hash = await hashPassword('Superb@tt1ng!');
console.log(hash); // $2b$12$...
const valid = await verifyPassword('Superb@tt1ng!', hash);
console.log(valid); // trueArgon2: The Modern Champion
Argon2 won the 2015 Password Hashing Competition and is the current state-of-the-art recommendation. It offers three variants: Argon2i (side-channel resistant), Argon2d (GPU-resistant), and Argon2id (hybrid, recommended). Argon2 allows tuning three independent parameters: time cost (iterations), memory cost (RAM in KiB), and parallelism (threads). Memory-hardness is the key advantage over bcrypt — it forces attackers to use large amounts of RAM per guess, making GPU-based cracking far less effective.
npm install argon2const argon2 = require('argon2');
// Hash with Argon2id (recommended variant)
async function hashPasswordArgon2(plaintext) {
return argon2.hash(plaintext, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MiB per hash
timeCost: 3, // 3 iterations
parallelism: 4 // 4 threads
});
}
// Verify
async function verifyPasswordArgon2(plaintext, storedHash) {
return argon2.verify(storedHash, plaintext);
}
// Rehash detection (if you upgrade parameters later)
async function loginWithRehash(plaintext, storedHash, userId) {
const valid = await argon2.verify(storedHash, plaintext);
if (!valid) throw new Error('Invalid credentials');
if (argon2.needsRehash(storedHash)) {
const newHash = await hashPasswordArgon2(plaintext);
await User.updateOne({ _id: userId }, { passwordHash: newHash });
}
return true;
}