Skip to content

Instantly share code, notes, and snippets.

@dhsrocha
Created February 4, 2023 10:44
Show Gist options
  • Save dhsrocha/6b61f88119f0bcc7cabfe28e6fa74591 to your computer and use it in GitHub Desktop.
Save dhsrocha/6b61f88119f0bcc7cabfe28e6fa74591 to your computer and use it in GitHub Desktop.
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Random;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
/**
* A utility class to hash passwords and check passwords vs hashed values. It uses a combination of
* hashing and unique salt. The algorithm used is PBKDF2WithHmacSHA1 which, although not the best
* for hashing password (vs. bcrypt) is still considered robust and <a
* href="https://security.stackexchange.com/a/6415/12614">recommended by NIST </a>. The hashed value
* has 256 bits.
*/
public class Passwords {
private static final Random RANDOM = new SecureRandom();
private static final int ITERATIONS = 10000;
private static final int KEY_LENGTH = 256;
/** static utility class */
private Passwords() {}
/**
* Returns a random salt to be used to hash a password.
*
* @return a 16 bytes random salt
*/
public static byte[] nextSalt() {
final byte[] salt = new byte[16];
RANDOM.nextBytes(salt);
return salt;
}
/**
* Returns a salted and hashed password using the provided hash.<br>
* Note - side effect: the password is destroyed (the char[] is filled with zeros)
*
* @param password the password to be hashed
* @param salt a 16 bytes salt, ideally obtained with the getNextSalt method
* @return the hashed password with a pinch of salt
*/
public static byte[] hash(final char[] password, final byte[] salt) {
final PBEKeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_LENGTH);
Arrays.fill(password, Character.MIN_VALUE);
try {
final SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
return skf.generateSecret(spec).getEncoded();
} catch (final NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new AssertionError("Error while hashing a password: " + e.getMessage(), e);
} finally {
spec.clearPassword();
}
}
/**
* Returns true if the given password and salt match the hashed value, false otherwise.<br>
* Note - side effect: the password is destroyed (the char[] is filled with zeros)
*
* @param password the password to check
* @param salt the salt used to hash the password
* @param expectedHash the expected hashed value of the password
* @return true if the given password and salt match the hashed value, false otherwise
*/
public static boolean isExpectedPassword(
final char[] password, final byte[] salt, final byte[] expectedHash) {
final byte[] pwdHash = hash(password, salt);
Arrays.fill(password, Character.MIN_VALUE);
if (pwdHash.length != expectedHash.length) return false;
for (int i = 0; i < pwdHash.length; i++) {
if (pwdHash[i] != expectedHash[i]) return false;
}
return true;
}
/**
* Generates a random password of a given length, using letters and digits.
*
* @param length the length of the password
* @return a random password
*/
public static String generateRandomPassword(final int length) {
final StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
final int c = RANDOM.nextInt(62);
if (c <= 9) {
sb.append(c);
} else if (c < 36) {
sb.append((char) ('a' + c - 10));
} else {
sb.append((char) ('A' + c - 36));
}
}
return sb.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment