Java Implementation
Java Implementation
Java's security architecture provides robust cryptographic primitives, but the standard library lacks modern password hashing algorithms. The Spring Security library offers excellent implementations, while dedicated libraries like password4j provide lightweight alternatives. Java's strong typing and exception handling enable building highly reliable password management systems.
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import java.util.Arrays;
import java.nio.charset.StandardCharsets;
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
public class SecurePasswordManager {
private static final String VERSION = "v3";
private static final int ARGON2_SALT_LENGTH = 16;
private static final int ARGON2_HASH_LENGTH = 32;
private static final int ARGON2_PARALLELISM = 4;
private static final int ARGON2_MEMORY = 65536;
private static final int ARGON2_ITERATIONS = 3;
private final String algorithm;
private final byte[] pepper;
private final PasswordEncoder argon2Encoder;
private final PasswordEncoder bcryptEncoder;
private final SecureRandom secureRandom;
public enum Algorithm {
ARGON2, BCRYPT
}
public SecurePasswordManager(Algorithm algorithm, byte[] pepper) {
this.algorithm = algorithm.name().toLowerCase();
this.pepper = pepper;
this.secureRandom = new SecureRandom();
// Initialize encoders
this.argon2Encoder = new Argon2PasswordEncoder(
ARGON2_SALT_LENGTH,
ARGON2_HASH_LENGTH,
ARGON2_PARALLELISM,
ARGON2_MEMORY,
ARGON2_ITERATIONS
);
this.bcryptEncoder = new BCryptPasswordEncoder(13, secureRandom);
}
public SecurePasswordManager(Algorithm algorithm) {
this(algorithm, null);
}
public String hashPassword(String password) {
if (password == null || password.isEmpty()) {
throw new IllegalArgumentException("Password cannot be empty");
}
if (password.length() > 1024) {
throw new IllegalArgumentException("Password too long");
}
String processedPassword = password;
// Apply pepper if configured
if (pepper != null) {
processedPassword = applyPepper(password);
}
String hash;
switch (algorithm) {
case "argon2":
hash = argon2Encoder.encode(processedPassword);
break;
case "bcrypt":
// BCrypt has 72-byte limit
if (processedPassword.getBytes(StandardCharsets.UTF_8).length > 72) {
processedPassword = hashForBcrypt(processedPassword);
}
hash = bcryptEncoder.encode(processedPassword);
break;
default:
throw new IllegalStateException("Unknown algorithm: " + algorithm);
}
return String.format("%s$%s$%s", VERSION, algorithm, hash);
}
public PasswordVerificationResult verifyPassword(String password, String storedHash) {
if (password == null || storedHash == null) {
return new PasswordVerificationResult(false, false);
}
try {
String[] parts = storedHash.split("\\$", 3);
if (parts.length != 3) {
// Legacy format
return verifyLegacy(password, storedHash);
}
String version = parts[0];
String hashAlgorithm = parts[1];
String hash = parts[2];
String processedPassword = password;
// Apply pepper if configured
if (pepper != null) {
processedPassword = applyPepper(password);
}
boolean valid = false;
boolean needsRehash = false;
switch (hashAlgorithm) {
case "argon2":
valid = argon2Encoder.matches(processedPassword, hash);
// Argon2 doesn't need rehashing with current params
break;
case "bcrypt":
if (processedPassword.getBytes(StandardCharsets.UTF_8).length > 72) {
processedPassword = hashForBcrypt(processedPassword);
}
valid = bcryptEncoder.matches(processedPassword, hash);
// Migrate bcrypt to argon2 if configured
if (valid && algorithm.equals("argon2")) {
needsRehash = true;
}
break;
default:
logError("Unknown algorithm in stored hash: " + hashAlgorithm);
return new PasswordVerificationResult(false, false);
}
return new PasswordVerificationResult(valid, needsRehash);
} catch (Exception e) {
logError("Verification failed: " + e.getMessage());
return new PasswordVerificationResult(false, false);
}
}
private String applyPepper(String password) {
try {
// Use PBKDF2 with pepper as salt for HMAC-like behavior
PBEKeySpec spec = new PBEKeySpec(
password.toCharArray(),
pepper,
1000,
256
);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = factory.generateSecret(spec).getEncoded();
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException("Pepper application failed", e);
}
}
private String hashForBcrypt(String input) {
try {
// Use SHA-256 to fit within bcrypt's limit
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 not available", e);
}
}
private PasswordVerificationResult verifyLegacy(String password, String legacyHash) {
// Example: Handle old bcrypt format
try {
if (bcryptEncoder.matches(password, legacyHash)) {
return new PasswordVerificationResult(true, true);
}
} catch (Exception e) {
// Legacy verification failed
}
return new PasswordVerificationResult(false, false);
}
private void logError(String message) {
System.err.println("[SECURITY] " + java.time.Instant.now() + " - " + message);
}
public String generateSecurePassword(int length) {
if (length < 8) {
throw new IllegalArgumentException("Password length must be at least 8");
}
String charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
StringBuilder password = new StringBuilder(length);
byte[] randomBytes = new byte[length];
secureRandom.nextBytes(randomBytes);
for (int i = 0; i < length; i++) {
int index = Math.abs(randomBytes[i]) % charset.length();
password.append(charset.charAt(index));
}
return password.toString();
}
public static class PasswordVerificationResult {
private final boolean valid;
private final boolean needsRehash;
public PasswordVerificationResult(boolean valid, boolean needsRehash) {
this.valid = valid;
this.needsRehash = needsRehash;
}
public boolean isValid() { return valid; }
public boolean needsRehash() { return needsRehash; }
}
// Demonstration
public static void main(String[] args) {
System.out.println("Java Password Hashing Demo\n");
SecurePasswordManager pm = new SecurePasswordManager(Algorithm.ARGON2);
String[] passwords = {
"SimplePassword123",
"VeryLongPassword" + "x".repeat(100),
"Unicode: café été",
"Special chars: !@#$%^&*()"
};
// Hash passwords
for (String password : passwords) {
try {
String hash = pm.hashPassword(password);
System.out.println("Password: " + password.substring(0, Math.min(20, password.length())) + "...");
System.out.println("Hash: " + hash.substring(0, Math.min(60, hash.length())) + "...\n");
// Verify
PasswordVerificationResult result = pm.verifyPassword(password, hash);
System.out.println("Verification: " + (result.isValid() ? "Valid" : "Invalid"));
System.out.println("Needs rehash: " + result.needsRehash() + "\n");
} catch (Exception e) {
System.err.println("Failed to process password: " + e.getMessage() + "\n");
}
}
// Generate secure password
String securePassword = pm.generateSecurePassword(20);
System.out.println("Generated secure password: " + securePassword);
}
}