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