JavaScript (Node.js) Implementation
JavaScript (Node.js) Implementation
Node.js applications require careful library selection due to the ecosystem's rapid evolution. The argon2 package provides excellent performance with native bindings, while bcryptjs offers a pure JavaScript alternative for environments where native modules are problematic. The built-in crypto module handles random generation and HMAC operations for pepper implementation.
const crypto = require('crypto');
const argon2 = require('argon2');
const bcrypt = require('bcryptjs');
class SecurePasswordManager {
constructor(options = {}) {
this.algorithm = options.algorithm || 'argon2';
this.pepper = options.pepper || null;
this.version = 'v3';
// Argon2 configuration
this.argon2Options = {
type: argon2.argon2id,
memoryCost: 65536, // 64MB
timeCost: 3, // iterations
parallelism: 4, // threads
hashLength: 32, // output length
saltLength: 16 // salt length
};
// Bcrypt configuration
this.bcryptRounds = 13;
}
async hashPassword(password) {
if (!password || typeof password !== 'string') {
throw new Error('Invalid password');
}
if (password.length > 1024) {
throw new Error('Password too long');
}
// Apply pepper if configured
let processedPassword = password;
if (this.pepper) {
processedPassword = this._applyPepper(password);
}
try {
let hash;
if (this.algorithm === 'argon2') {
hash = await argon2.hash(processedPassword, this.argon2Options);
return `${this.version}$argon2$${hash}`;
} else if (this.algorithm === 'bcrypt') {
// Handle bcrypt's 72-byte limit
if (Buffer.byteLength(processedPassword, 'utf8') > 72) {
const hash = crypto.createHash('sha256')
.update(processedPassword)
.digest('base64');
processedPassword = hash;
}
hash = await bcrypt.hash(processedPassword, this.bcryptRounds);
return `${this.version}$bcrypt$${hash}`;
} else {
throw new Error(`Unknown algorithm: ${this.algorithm}`);
}
} catch (error) {
this._logError(`Hashing failed: ${error.message}`);
throw new Error('Password hashing failed');
}
}
async verifyPassword(password, storedHash) {
if (!password || !storedHash) {
return { valid: false, needsRehash: false };
}
try {
const parts = storedHash.split('$');
if (parts.length !== 3) {
// Legacy format
return await this._verifyLegacy(password, storedHash);
}
const [version, algorithm, hash] = parts;
// Apply pepper if configured
let processedPassword = password;
if (this.pepper) {
processedPassword = this._applyPepper(password);
}
if (algorithm === 'argon2') {
const valid = await argon2.verify(hash, processedPassword);
if (valid) {
// Check if rehashing needed
const needsRehash = await argon2.needsRehash(
hash,
this.argon2Options
);
return { valid: true, needsRehash };
}
return { valid: false, needsRehash: false };
} else if (algorithm === 'bcrypt') {
// Handle bcrypt's 72-byte limit
if (Buffer.byteLength(processedPassword, 'utf8') > 72) {
const hash = crypto.createHash('sha256')
.update(processedPassword)
.digest('base64');
processedPassword = hash;
}
const valid = await bcrypt.compare(processedPassword, hash);
if (valid && this.algorithm === 'argon2') {
// Migrate from bcrypt to argon2
return { valid: true, needsRehash: true };
}
return { valid, needsRehash: false };
} else {
this._logError(`Unknown algorithm: ${algorithm}`);
return { valid: false, needsRehash: false };
}
} catch (error) {
this._logError(`Verification failed: ${error.message}`);
return { valid: false, needsRehash: false };
}
}
_applyPepper(password) {
const hmac = crypto.createHmac('sha256', this.pepper);
hmac.update(password, 'utf8');
return hmac.digest('base64');
}
async _verifyLegacy(password, legacyHash) {
try {
// Example: Legacy bcrypt without version prefix
const valid = await bcrypt.compare(password, legacyHash);
if (valid) {
return { valid: true, needsRehash: true };
}
} catch (error) {
// Legacy verification failed
}
return { valid: false, needsRehash: false };
}
_logError(message) {
console.error(`[SECURITY] ${new Date().toISOString()} - ${message}`);
}
generateSecurePassword(length = 16) {
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
const randomBytes = crypto.randomBytes(length);
let password = '';
for (let i = 0; i < length; i++) {
password += charset[randomBytes[i] % charset.length];
}
return password;
}
}
// Example usage and demonstration
async function demonstrateNodeImplementation() {
console.log('Node.js Password Hashing Demo\n');
// Initialize password manager
const pm = new SecurePasswordManager({ algorithm: 'argon2' });
// Test passwords
const passwords = [
'SimplePassword123',
'VeryLongPassword'.repeat(10),
'Unicode: café été 你好',
'Special chars: !@#$%^&*()'
];
const hashedPasswords = new Map();
// Hash passwords
for (const password of passwords) {
try {
const hash = await pm.hashPassword(password);
hashedPasswords.set(password, hash);
console.log(`Password: ${password.substring(0, 20)}...`);
console.log(`Hash: ${hash.substring(0, 60)}...\n`);
} catch (error) {
console.error(`Failed to hash password: ${error.message}\n`);
}
}
// Verify passwords
console.log('Verification Tests:');
for (const [password, hash] of hashedPasswords) {
const result = await pm.verifyPassword(password, hash);
console.log(`Password: ${password.substring(0, 20)}...`);
console.log(`Valid: ${result.valid}, Needs rehash: ${result.needsRehash}`);
}
// Test wrong password
const firstHash = hashedPasswords.values().next().value;
const wrongResult = await pm.verifyPassword('wrong password', firstHash);
console.log(`\nWrong password - Valid: ${wrongResult.valid}`);
// Generate secure password
const securePassword = pm.generateSecurePassword(20);
console.log(`\nGenerated secure password: ${securePassword}`);
}
// Run demonstration
demonstrateNodeImplementation().catch(console.error);