Skip to content
Advertisement

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 : [B@7bb894a0

When I try to connect the user :

Existing salt: [B@d433036
// retry connection
Existing salt: [B@355bae9a

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;
    }
}

Advertisement

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));
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement