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