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;
    }
}