package com.mzh.library.util; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.SecureRandom; import java.util.Arrays; import java.util.Base64; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; public final class PasswordHasher { private static final String ALGORITHM = "PBKDF2WithHmacSHA256"; private static final String PREFIX = "pbkdf2_sha256"; private static final int DEFAULT_ITERATIONS = 60000; private static final int SALT_BYTES = 16; private static final int HASH_BYTES = 32; private PasswordHasher() { } public static String hash(String password) { byte[] salt = new byte[SALT_BYTES]; new SecureRandom().nextBytes(salt); byte[] hash = derive(password, salt, DEFAULT_ITERATIONS); return PREFIX + "$" + DEFAULT_ITERATIONS + "$" + Base64.getEncoder().encodeToString(salt) + "$" + Base64.getEncoder().encodeToString(hash); } public static boolean verify(String password, String storedHash) { if (password == null || storedHash == null || storedHash.trim().isEmpty()) { return false; } String[] parts = storedHash.split("\\$"); if (parts.length != 4 || !PREFIX.equals(parts[0])) { return false; } try { int iterations = Integer.parseInt(parts[1]); byte[] salt = Base64.getDecoder().decode(parts[2]); byte[] expected = Base64.getDecoder().decode(parts[3]); byte[] actual = derive(password, salt, iterations); return MessageDigest.isEqual(expected, actual); } catch (IllegalArgumentException ex) { return false; } } private static byte[] derive(String password, byte[] salt, int iterations) { char[] passwordChars = password.toCharArray(); try { PBEKeySpec spec = new PBEKeySpec(passwordChars, salt, iterations, HASH_BYTES * 8); return SecretKeyFactory.getInstance(ALGORITHM).generateSecret(spec).getEncoded(); } catch (GeneralSecurityException ex) { throw new IllegalStateException("Unable to hash password", ex); } finally { Arrays.fill(passwordChars, '\0'); } } }