package org.keycloak.models.utils;
import org.keycloak.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
/**
* <p>
* Encoder that uses PBKDF2 function to cryptographically derive passwords.
* </p>
* <p>Passwords are returned with a Base64 encoding.</p>
*
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
*
*/
public class Pbkdf2PasswordEncoder {
public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
public static final String RNG_ALGORITHM = "SHA1PRNG";
private static final int DERIVED_KEY_SIZE = 512;
private static final int ITERATIONS = 1;
private final int iterations;
private byte[] salt;
public Pbkdf2PasswordEncoder(byte[] salt, int iterations) {
this.salt = salt;
this.iterations = iterations;
}
public Pbkdf2PasswordEncoder(byte[] salt) {
this(salt, ITERATIONS);
}
/**
* Encode the raw password provided
* @param rawPassword The password used as a master key to derive into a session key
* @return encoded password in Base64
*/
public String encode(String rawPassword, int iterations) {
String encodedPassword;
KeySpec spec = new PBEKeySpec(rawPassword.toCharArray(), salt, iterations, DERIVED_KEY_SIZE);
try {
byte[] key = getSecretKeyFactory().generateSecret(spec).getEncoded();
encodedPassword = Base64.encodeBytes(key);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("Credential could not be encoded");
}
return encodedPassword;
}
public String encode(String rawPassword) {
return encode(rawPassword, iterations);
}
/**
* Encode the password provided and compare with the hash stored into the database
* @param rawPassword The password provided
* @param encodedPassword Encoded hash stored into the database
* @return true if the password is valid, otherwise false for invalid credentials
*/
public boolean verify(String rawPassword, String encodedPassword) {
return encode(rawPassword).equals(encodedPassword);
}
/**
* Encode the password provided and compare with the hash stored into the database
* @param rawPassword The password provided
* @param encodedPassword Encoded hash stored into the database
* @return true if the password is valid, otherwise false for invalid credentials
*/
public boolean verify(String rawPassword, String encodedPassword, int iterations) {
return encode(rawPassword, iterations).equals(encodedPassword);
}
/**
* Generate a salt for each password
* @return cryptographically strong random number
*/
public static byte[] getSalt() {
byte[] buffer = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(buffer);
return buffer;
}
private static SecretKeyFactory getSecretKeyFactory() {
try {
return SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("PBKDF2 algorithm not found");
}
}
}