Implementing Secure Password Policies

Implementing Secure Password Policies

Technical password storage security must be complemented by appropriate password policies that balance security with usability. Traditional complexity requirements often lead to predictable patterns that sophisticated attacks easily exploit. Modern password policies focus on length over complexity, recognizing that longer passphrases provide better security while being easier for users to remember.

Password strength estimation should use sophisticated algorithms that consider common patterns, dictionary words, and predictable substitutions. Libraries like zxcvbn provide realistic password strength estimates by analyzing passwords against common patterns and leaked password databases. Real-time password strength feedback helps users create stronger passwords without frustrating complexity requirements.

// Example: Implementing comprehensive password policy validation
import com.nulabinc.zxcvbn.Zxcvbn;
import java.util.regex.Pattern;
import java.security.MessageDigest;
import java.util.Set;
import java.util.HashSet;

public class PasswordPolicyValidator {
    private final Zxcvbn zxcvbn = new Zxcvbn();
    private final Set<String> commonPasswords;
    private final Set<String> breachedPasswordHashes;
    
    // Configurable policy parameters
    private final int minLength = 12;
    private final int minStrengthScore = 3; // 0-4 scale
    private final int maxLength = 128;
    private final boolean checkBreachedPasswords = true;
    
    public PasswordPolicyValidator() {
        // Load common passwords list
        this.commonPasswords = loadCommonPasswords();
        this.breachedPasswordHashes = loadBreachedPasswordHashes();
    }
    
    public ValidationResult validatePassword(String password, UserContext context) {
        ValidationResult result = new ValidationResult();
        
        // Length checks
        if (password.length() < minLength) {
            result.addError(String.format("Password must be at least %d characters", minLength));
        }
        
        if (password.length() > maxLength) {
            result.addError(String.format("Password cannot exceed %d characters", maxLength));
        }
        
        // Check against common passwords
        if (commonPasswords.contains(password.toLowerCase())) {
            result.addError("This password is too common");
        }
        
        // Context-aware checks
        if (containsUserInfo(password, context)) {
            result.addError("Password cannot contain personal information");
        }
        
        // Strength estimation with context
        String[] userInputs = getUserInputs(context);
        com.nulabinc.zxcvbn.Strength strength = zxcvbn.measure(password, userInputs);
        
        if (strength.getScore() < minStrengthScore) {
            result.addError("Password is too weak");
            result.setSuggestions(strength.getFeedback().getSuggestions());
        }
        
        result.setStrengthScore(strength.getScore());
        result.setEstimatedCrackTime(strength.getCrackTimesDisplay().getOfflineSlowHashing1e4PerSecond());
        
        // Check against breach databases
        if (checkBreachedPasswords && isBreachedPassword(password)) {
            result.addError("This password has been found in data breaches");
        }
        
        return result;
    }
    
    private boolean containsUserInfo(String password, UserContext context) {
        String lowerPassword = password.toLowerCase();
        
        // Check for username
        if (context.getUsername() != null && 
            lowerPassword.contains(context.getUsername().toLowerCase())) {
            return true;
        }
        
        // Check for email parts
        if (context.getEmail() != null) {
            String emailLocal = context.getEmail().split("@")[0].toLowerCase();
            if (lowerPassword.contains(emailLocal)) {
                return true;
            }
        }
        
        // Check for name parts
        if (context.getFullName() != null) {
            String[] nameParts = context.getFullName().toLowerCase().split("\\s+");
            for (String part : nameParts) {
                if (part.length() > 2 && lowerPassword.contains(part)) {
                    return true;
                }
            }
        }
        
        return false;
    }
    
    private boolean isBreachedPassword(String password) {
        try {
            // Use k-anonymity approach (like HaveIBeenPwned API)
            MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
            byte[] hashBytes = sha1.digest(password.getBytes("UTF-8"));
            String fullHash = bytesToHex(hashBytes);
            String hashPrefix = fullHash.substring(0, 5);
            
            // Check if hash exists in breached set
            // In production, this would query an API or local database
            return breachedPasswordHashes.contains(fullHash);
            
        } catch (Exception e) {
            // Log error but don't block password creation
            logger.error("Failed to check breached passwords", e);
            return false;
        }
    }
    
    public static class ValidationResult {
        private final List<String> errors = new ArrayList<>();
        private List<String> suggestions = new ArrayList<>();
        private int strengthScore;
        private String estimatedCrackTime;
        
        public boolean isValid() {
            return errors.isEmpty();
        }
        
        // Getters, setters, and utility methods...
    }
}