95 lines
3.0 KiB
Java
95 lines
3.0 KiB
Java
package envoy.server.util;
|
|
|
|
import java.math.BigInteger;
|
|
import java.security.*;
|
|
|
|
import javax.crypto.SecretKeyFactory;
|
|
import javax.crypto.spec.PBEKeySpec;
|
|
|
|
/**
|
|
* Provides a password hashing and comparison mechanism using the {@code PBKDF2WithHmacSHA1}
|
|
* algorithm.
|
|
*
|
|
* @author Kai S. K. Engelbart
|
|
* @since Envoy Server Standalone v0.1-beta
|
|
*/
|
|
public final class PasswordUtil {
|
|
|
|
private static final int ITERATIONS = 1000;
|
|
private static final int KEY_LENGTH = 64 * 8;
|
|
private static final String SALT_ALGORITHM = "SHA1PRNG";
|
|
private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA1";
|
|
|
|
private PasswordUtil() {}
|
|
|
|
/**
|
|
* Validates a password against a stored password hash
|
|
*
|
|
* @param current the password to validate
|
|
* @param stored the hash to validate against
|
|
* @return {@code true} if the password is correct
|
|
* @since Envoy Server Standalone v0.1-beta
|
|
*/
|
|
public static boolean validate(String current, String stored) {
|
|
try {
|
|
final String[] parts = stored.split(":");
|
|
final int iterations = Integer.parseInt(parts[0]);
|
|
final byte[] salt = fromHex(parts[1]);
|
|
final byte[] hash = fromHex(parts[2]);
|
|
|
|
final var spec =
|
|
new PBEKeySpec(current.toCharArray(), salt, iterations, KEY_LENGTH);
|
|
final var skf = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
|
|
final byte[] testHash = skf.generateSecret(spec).getEncoded();
|
|
|
|
int diff = hash.length ^ testHash.length;
|
|
for (int i = 0; i < hash.length && i < testHash.length; ++i)
|
|
diff |= hash[i] ^ testHash[i];
|
|
|
|
return diff == 0;
|
|
} catch (final GeneralSecurityException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a parameterized salted password hash.
|
|
*
|
|
* @param password the password to hash
|
|
* @return a result string in the form of {@code iterations:salt:hash}
|
|
* @since Envoy Server Standalone v0.1-beta
|
|
*/
|
|
public static String hash(String password) {
|
|
try {
|
|
final byte[] salt = salt();
|
|
final var spec =
|
|
new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
|
|
final var skf = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
|
|
final byte[] hash = skf.generateSecret(spec).getEncoded();
|
|
return ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash);
|
|
} catch (final GeneralSecurityException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private static byte[] salt() throws NoSuchAlgorithmException {
|
|
final SecureRandom sr = SecureRandom.getInstance(SALT_ALGORITHM);
|
|
final byte[] salt = new byte[16];
|
|
sr.nextBytes(salt);
|
|
return salt;
|
|
}
|
|
|
|
private static String toHex(byte[] bytes) {
|
|
final String hex = new BigInteger(1, bytes).toString(16);
|
|
final int padding = bytes.length * 2 - hex.length();
|
|
return padding > 0 ? String.format("%0" + padding + "d", 0) + hex : hex;
|
|
}
|
|
|
|
private static byte[] fromHex(String hex) {
|
|
final byte[] bytes = new byte[hex.length() / 2];
|
|
for (int i = 0; i < bytes.length; ++i)
|
|
bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
|
|
return bytes;
|
|
}
|
|
}
|