Secure Password Handling
Secure Password Handling
Password-based authentication remains the most common authentication method, despite its known weaknesses. Implementing secure password handling requires careful attention to storage, transmission, and validation. Never store passwords in plain text or using reversible encryption. Instead, use strong one-way hashing algorithms specifically designed for password storage.
# Python - Secure Password Handling
import bcrypt
import secrets
import re
from typing import Tuple, Dict
from datetime import datetime, timedelta
import argon2
from argon2 import PasswordHasher
class SecurePasswordManager:
def __init__(self):
# Use Argon2id - winner of Password Hashing Competition
self.ph = PasswordHasher(
time_cost=3, # Number of iterations
memory_cost=65536, # Memory usage in KB
parallelism=4, # Number of parallel threads
hash_len=32, # Length of the hash
salt_len=16 # Length of the salt
)
# Password policy configuration
self.min_length = 12
self.require_uppercase = True
self.require_lowercase = True
self.require_digits = True
self.require_special = True
self.password_history_size = 5
def validate_password_strength(self, password: str) -> Tuple[bool, List[str]]:
"""Validate password meets security requirements"""
errors = []
if len(password) < self.min_length:
errors.append(f"Password must be at least {self.min_length} characters")
if self.require_uppercase and not re.search(r'[A-Z]', password):
errors.append("Password must contain uppercase letters")
if self.require_lowercase and not re.search(r'[a-z]', password):
errors.append("Password must contain lowercase letters")
if self.require_digits and not re.search(r'\d', password):
errors.append("Password must contain digits")
if self.require_special and not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
errors.append("Password must contain special characters")
# Check for common patterns
common_patterns = [
r'(.)\1{2,}', # Repeated characters (aaa, 111)
r'(012|123|234|345|456|567|678|789|890)', # Sequential numbers
r'(abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz)', # Sequential letters
]
for pattern in common_patterns:
if re.search(pattern, password.lower()):
errors.append("Password contains common patterns")
break
# Check against common passwords
with open('common_passwords.txt', 'r') as f:
common_passwords = {line.strip().lower() for line in f}
if password.lower() in common_passwords:
errors.append("Password is too common")
return len(errors) == 0, errors
def hash_password(self, password: str) -> str:
"""Hash password using Argon2id"""
return self.ph.hash(password)
def verify_password(self, password: str, hash: str) -> bool:
"""Verify password against hash"""
try:
self.ph.verify(hash, password)
# Check if rehashing is needed (parameters changed)
if self.ph.check_needs_rehash(hash):
# Return indicator that rehashing is recommended
return True, True
return True, False
except argon2.exceptions.VerifyMismatchError:
return False, False
except Exception:
# Log error but don't reveal details
return False, False
def check_password_history(self, new_password: str,
password_history: List[str]) -> bool:
"""Check if password was recently used"""
for old_hash in password_history[-self.password_history_size:]:
try:
self.ph.verify(old_hash, new_password)
return False # Password was used before
except:
continue
return True # Password is new
def generate_secure_password(self, length: int = 16) -> str:
"""Generate cryptographically secure password"""
# Character sets
uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
lowercase = 'abcdefghijklmnopqrstuvwxyz'
digits = '0123456789'
special = '!@#$%^&*(),.?":{}|<>'
# Ensure at least one character from each required set
password = [
secrets.choice(uppercase),
secrets.choice(lowercase),
secrets.choice(digits),
secrets.choice(special)
]
# Fill remaining length with random characters
all_chars = uppercase + lowercase + digits + special
for _ in range(length - 4):
password.append(secrets.choice(all_chars))
# Shuffle to avoid predictable patterns
secrets.SystemRandom().shuffle(password)
return ''.join(password)
// JavaScript - Secure Password Handling
const bcrypt = require('bcrypt');
const argon2 = require('argon2');
const crypto = require('crypto');
class SecurePasswordManager {
constructor() {
// Argon2id configuration
this.argon2Options = {
type: argon2.argon2id,
memoryCost: 2 ** 16, // 64 MB
timeCost: 3,
parallelism: 4,
hashLength: 32,
saltLength: 16
};
// Password policy
this.policy = {
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireDigits: true,
requireSpecial: true,
maxConsecutiveChars: 2,
passwordHistorySize: 5
};
}
async validatePasswordStrength(password) {
const errors = [];
// Length check
if (password.length < this.policy.minLength) {
errors.push(`Password must be at least ${this.policy.minLength} characters`);
}
// Character requirements
if (this.policy.requireUppercase && !/[A-Z]/.test(password)) {
errors.push('Password must contain uppercase letters');
}
if (this.policy.requireLowercase && !/[a-z]/.test(password)) {
errors.push('Password must contain lowercase letters');
}
if (this.policy.requireDigits && !/\d/.test(password)) {
errors.push('Password must contain digits');
}
if (this.policy.requireSpecial && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
errors.push('Password must contain special characters');
}
// Check for repeated characters
const repeatedChars = /(.)\1{2,}/;
if (repeatedChars.test(password)) {
errors.push('Password contains too many repeated characters');
}
// Check for sequences
const sequences = [
'0123456789',
'abcdefghijklmnopqrstuvwxyz',
'qwertyuiop',
'asdfghjkl',
'zxcvbnm'
];
const lowercasePassword = password.toLowerCase();
for (const seq of sequences) {
for (let i = 0; i <= seq.length - 4; i++) {
if (lowercasePassword.includes(seq.substring(i, i + 4))) {
errors.push('Password contains common sequences');
break;
}
}
}
// Entropy calculation
const entropy = this.calculatePasswordEntropy(password);
if (entropy < 50) {
errors.push('Password is not complex enough');
}
return {
isValid: errors.length === 0,
errors,
entropy
};
}
calculatePasswordEntropy(password) {
// Calculate password entropy in bits
let charSpace = 0;
if (/[a-z]/.test(password)) charSpace += 26;
if (/[A-Z]/.test(password)) charSpace += 26;
if (/\d/.test(password)) charSpace += 10;
if (/[^a-zA-Z0-9]/.test(password)) charSpace += 32;
const entropy = password.length * Math.log2(charSpace);
return Math.round(entropy);
}
async hashPassword(password) {
// Use Argon2id for new passwords
return await argon2.hash(password, this.argon2Options);
}
async verifyPassword(password, hash) {
try {
// Support both Argon2 and bcrypt for migration
if (hash.startsWith('$argon2')) {
return await argon2.verify(hash, password);
} else if (hash.startsWith('$2')) {
// Legacy bcrypt hash
const isValid = await bcrypt.compare(password, hash);
return { isValid, needsRehash: true };
}
return { isValid: false };
} catch (error) {
console.error('Password verification error:', error);
return { isValid: false };
}
}
generateSecurePassword(length = 16) {
const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const lowercase = 'abcdefghijklmnopqrstuvwxyz';
const digits = '0123456789';
const special = '!@#$%^&*(),.?":{}|<>';
const allChars = uppercase + lowercase + digits + special;
// Ensure complexity requirements
let password = [
uppercase[crypto.randomInt(uppercase.length)],
lowercase[crypto.randomInt(lowercase.length)],
digits[crypto.randomInt(digits.length)],
special[crypto.randomInt(special.length)]
];
// Fill remaining characters
for (let i = password.length; i < length; i++) {
password.push(allChars[crypto.randomInt(allChars.length)]);
}
// Shuffle array using Fisher-Yates
for (let i = password.length - 1; i > 0; i--) {
const j = crypto.randomInt(i + 1);
[password[i], password[j]] = [password[j], password[i]];
}
return password.join('');
}
async checkPasswordHistory(password, passwordHistory) {
// Check against previous password hashes
for (const oldHash of passwordHistory.slice(-this.policy.passwordHistorySize)) {
try {
if (await argon2.verify(oldHash, password)) {
return false; // Password was used before
}
} catch {
// Continue if verification fails
}
}
return true; // Password is new
}
}