Skip to content

Keycloak custom PasswordHashProvider, salt change on each connection

I am trying to create a custom PasswordHashProvider. I succeeded in creating the component, the algorithm is working, the password saved in the database is correct.

A Salt is stored in a database, but I don’t understand how. I thought it was the base 64 salt, but it doesn’t match.

database secret_data : {"value":"v8Po+hMGZbS2tWr4IGRXqy3FJyQIavjsrpXHwomWHwRRs+iRVInd+9naF681EENm76lF1omK+BqsPIRrtKH1VQ==","salt":"DEiO17h4/+6gwzxn"}
database credential_data : {"hashIterations":5000,"algorithm":"message-digest"} 

I add logs in the console when creating a password and when connecting a user. The salt is generated, stored, but the one retrieved during connection is different on each try.

When I register the user secret :

New salt : [[email protected]

When I try to connect the user :

Existing salt: [[email protected]
// retry connection
Existing salt: [[email protected]

Does anyone know how salt is stored?

My class (for tests I force iterations to 5000) :

import org.keycloak.credential.hash.PasswordHashProvider;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.credential.PasswordCredentialModel;

import java.util.Base64;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.nio.charset.StandardCharsets;

import java.math.BigInteger; 
import java.security.NoSuchAlgorithmException; 

public class MessageDigestPasswordHashProvider implements PasswordHashProvider {
    private final int defaultIterations;
    private final String providerId;
    private byte[] salt;

    public MessageDigestPasswordHashProvider(String providerId, int defaultIterations) {
        this.providerId = providerId;
        this.defaultIterations = defaultIterations;
    }

    @Override
    public boolean policyCheck(PasswordPolicy policy, PasswordCredentialModel credential) {
        int policyHashIterations = 5000;
        return credential.getPasswordCredentialData().getHashIterations() == policyHashIterations
                && providerId.equals(credential.getPasswordCredentialData().getAlgorithm());
    }

    @Override
    public PasswordCredentialModel encodedCredential(String rawPassword, int iterations) {
        iterations = 5000;
        byte[] salt = generateSalt();
        this.salt = salt;
        System.out.print("New salt : ");
        System.out.println(salt);
        String encodedPassword = encode(rawPassword, iterations);

        return PasswordCredentialModel.createFromValues(providerId, salt, iterations, encodedPassword);
    }

    @Override
    public String encode(String rawPassword, int iterations) {
        iterations = 5000;
        byte[] salt = this.salt;
        System.out.print("Salt fonction encode :");
        System.out.println(salt);
        String salted = rawPassword + "{" + salt.toString() + "}";
        byte[] digest = getSha512(salted.getBytes());
        System.out.println(iterations);
        for (int i = 1; i < iterations; i++) {
            digest = getSha512(concat(digest, salted.getBytes()));
        }

        return Base64.getEncoder().encodeToString(digest);
    }

    @Override
    public boolean verify(String rawPassword, PasswordCredentialModel credential) {
        this.salt = credential.getSalt();
        System.out.print("Existing salt: ");
        System.out.println(this.salt);
        String encodedPassword = encode(rawPassword, credential.getHashIterations());
        return encodedPassword.equals(credential.getValue());
    }


    private static byte[] generateSalt() {
        SecureRandom random = new SecureRandom();
        byte salt[] = new byte[12];
        random.nextBytes(salt);
        return salt;
    }
    @Override
    public void close() {

    }
    public static byte[] getSha512(byte[] value) {
        try { 
            MessageDigest md = MessageDigest.getInstance("SHA-512"); 
            byte[] messageDigest = md.digest(value); 
            return messageDigest;
        } 
        catch (NoSuchAlgorithmException e) { 
            throw new RuntimeException(e); 
        } 
    }
    public static byte[] concat(byte[] a, byte[] b) {
        byte[] c = new byte[a.length + b.length];
        System.arraycopy(a, 0, c, 0, a.length);
        System.arraycopy(b, 0, c, a.length, b.length);
        return c;
    }
}

Answer

A Salt is stored in a database, but I don’t understand how. I thought it was the base 64 salt, but it doesn’t match.

It is based 64.

The salt is generated, stored, but the one retrieved during connection is different on each try.

The problem is this:

System.out.print("New salt : ");
System.out.println(salt);

you are not print the actually content of the salt you are print the object “salt”.

Try instead:

System.out.println(Arrays.toString(this.salt));