I need to hash passwords for storage in a database. How can I do this in Java?
I was hoping to take the plain text password, add a random salt, then store the salt a
Here is a complete implementation with two methods doing exactly what you want:
String getSaltedHash(String password)
boolean checkPassword(String password, String stored)
The point is that even if an attacker gets access to both your database and source code, the passwords are still safe.
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import org.apache.commons.codec.binary.Base64;
public class Password {
// The higher the number of iterations the more
// expensive computing the hash is for us and
// also for an attacker.
private static final int iterations = 20*1000;
private static final int saltLen = 32;
private static final int desiredKeyLen = 256;
/** Computes a salted PBKDF2 hash of given plaintext password
suitable for storing in a database.
Empty passwords are not supported. */
public static String getSaltedHash(String password) throws Exception {
byte[] salt = SecureRandom.getInstance("SHA1PRNG").generateSeed(saltLen);
// store the salt with the password
return Base64.encodeBase64String(salt) + "$" + hash(password, salt);
}
/** Checks whether given plaintext password corresponds
to a stored salted hash of the password. */
public static boolean check(String password, String stored) throws Exception{
String[] saltAndHash = stored.split("\\$");
if (saltAndHash.length != 2) {
throw new IllegalStateException(
"The stored password must have the form 'salt$hash'");
}
String hashOfInput = hash(password, Base64.decodeBase64(saltAndHash[0]));
return hashOfInput.equals(saltAndHash[1]);
}
// using PBKDF2 from Sun, an alternative is https://github.com/wg/scrypt
// cf. http://www.unlimitednovelty.com/2012/03/dont-use-bcrypt.html
private static String hash(String password, byte[] salt) throws Exception {
if (password == null || password.length() == 0)
throw new IllegalArgumentException("Empty passwords are not supported.");
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey key = f.generateSecret(new PBEKeySpec(
password.toCharArray(), salt, iterations, desiredKeyLen));
return Base64.encodeBase64String(key.getEncoded());
}
}
We are storing 'salt$iterated_hash(password, salt)'
. The salt are 32 random bytes and it's purpose is that if two different people choose the same password, the stored passwords will still look different.
The iterated_hash
, which is basically hash(hash(hash(... hash(password, salt) ...)))
makes it very expensive for a potential attacker who has access to your database to guess passwords, hash them, and look up hashes in the database. You have to compute this iterated_hash
every time a user logs in, but it doesn't cost you that much compared to the attacker who spends nearly 100% of their time computing hashes.