Modern Password Hashing Algorithms
Modern Password Hashing Algorithms
Modern password hashing algorithms incorporate multiple defensive mechanisms against various attack vectors. PBKDF2 (Password-Based Key Derivation Function 2) applies a pseudorandom function repeatedly, with the iteration count providing tunable computational cost. While PBKDF2 remains acceptable for password storage, newer algorithms offer better resistance against specialized hardware attacks.
bcrypt, based on the Blowfish cipher, introduces memory-hardness to password hashing. By requiring significant memory access during computation, bcrypt resists GPU-based attacks more effectively than PBKDF2. The algorithm includes salt generation and formatting in its standard implementation, reducing implementation errors. bcrypt's cost factor allows exponential scaling of computational requirements, with each increment doubling the required time.
// Example: Implementing bcrypt with automatic upgrade mechanism
const bcrypt = require('bcrypt');
const argon2 = require('argon2');
class PasswordManager {
constructor() {
// Current security parameters
this.bcryptRounds = 12;
this.argon2Options = {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3,
parallelism: 4,
hashLength: 32
};
}
async hashPassword(password) {
// Use Argon2 for new passwords
try {
const hash = await argon2.hash(password, this.argon2Options);
return hash;
} catch (error) {
// Fallback to bcrypt if Argon2 fails
console.error('Argon2 hashing failed, falling back to bcrypt:', error);
return await bcrypt.hash(password, this.bcryptRounds);
}
}
async verifyPassword(password, hash) {
// Detect hash type and verify accordingly
if (hash.startsWith('$argon2')) {
return await this.verifyArgon2(password, hash);
} else if (hash.startsWith('$2a$') || hash.startsWith('$2b$')) {
return await this.verifyBcrypt(password, hash);
} else {
// Unknown hash format
throw new Error('Unsupported hash format');
}
}
async verifyArgon2(password, hash) {
try {
const valid = await argon2.verify(hash, password);
if (valid) {
// Check if rehashing needed due to parameter changes
const needsRehash = await argon2.needsRehash(
hash,
this.argon2Options
);
return { valid: true, needsRehash };
}
return { valid: false };
} catch (error) {
console.error('Argon2 verification error:', error);
return { valid: false };
}
}
async verifyBcrypt(password, hash) {
const valid = await bcrypt.compare(password, hash);
if (valid) {
// Check if upgrade to Argon2 is needed
const rounds = this.extractBcryptRounds(hash);
const needsRehash = rounds < this.bcryptRounds || true; // Always upgrade to Argon2
return { valid, needsRehash };
}
return { valid: false };
}
extractBcryptRounds(hash) {
const match = hash.match(/^\$2[aby]?\$(\d+)\$/);
return match ? parseInt(match[1], 10) : 0;
}
// Usage in authentication flow
async authenticateUser(username, password) {
const user = await this.getUserByUsername(username);
if (!user) {
// Perform dummy hash to prevent timing attacks
await this.hashPassword('dummy');
return false;
}
const result = await this.verifyPassword(password, user.passwordHash);
if (result.valid) {
if (result.needsRehash) {
// Rehash password with current parameters
const newHash = await this.hashPassword(password);
await this.updateUserPasswordHash(user.id, newHash);
}
return true;
}
return false;
}
}