You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Keycloak自定义PasswordHashProvider技术问题:Salt存储机制解析及登录时Salt显示不一致排查

问题分析与解决方案

Hey there, let's break down what's going on with your Keycloak custom PasswordHashProvider and the salt confusion:

1. Why your logs show [B@xxx for salt

When you print a byte array directly with System.out.println(salt), you're not seeing the actual salt content—you're seeing the object reference hash code of the byte array (the [B denotes a byte array type). Even if the underlying salt bytes are identical, this string will change if the byte array object itself is recreated (like when loading from the database), which is why your login logs show different values.

To see the actual salt content that matches what's stored in the database, print the Base64 encoded version instead:

System.out.println("New salt : " + Base64.getEncoder().encodeToString(salt));

This will output the same string you see in your database's secret_data field.

2. How Keycloak stores salt

Keycloak automatically handles encoding your byte[] salt to Base64 when storing it in the database. The value "DEiO17h4/+6gwzxn" in your secret_data is exactly the Base64-encoded version of the 12-byte salt you generated in generateSalt()—this behavior is expected and correct.

3. Critical bug in your code

The biggest issue is in your encode method, where you're using salt.toString() to concatenate the salt:

String salted = rawPassword + "{" + salt.toString() + "}";

As we covered, salt.toString() gives you the object reference, not the actual salt bytes. This means:

  • When registering a user, you're hashing the password with a string like mypassword{[B@7bb894a0}
  • When logging in, you'll generate a different string like mypassword{[B@d433036} even with the same salt bytes, leading to a mismatched hash and failed verification

Fix this by using the actual salt bytes (either as Base64 string or directly as bytes):

// Option 1: Use Base64 encoded salt string
String saltStr = Base64.getEncoder().encodeToString(salt);
String salted = rawPassword + "{" + saltStr + "}";

// Option 2 (more secure, avoids string encoding edge cases): Work directly with bytes
byte[] rawBytes = rawPassword.getBytes(StandardCharsets.UTF_8);
byte[] saltedBytes = concat(rawBytes, salt);
byte[] digest = getSha512(saltedBytes);

4. Additional optimizations

  • Stop hardcoding iterations = 5000 in policyCheck and encodedCredential—use the passed-in parameters or pull values from the password policy, otherwise your policy configuration will be ignored.
  • Avoid storing the salt in a member variable (this.salt)—Keycloak providers can be shared across multiple requests, leading to race conditions. Instead, pass the salt directly to your encode method.

Here's a cleaned-up version of your verify/encode methods to fix this:

@Override
public boolean verify(String rawPassword, PasswordCredentialModel credential) {
    byte[] storedSalt = credential.getSalt();
    System.out.println("Existing salt (Base64): " + Base64.getEncoder().encodeToString(storedSalt));
    String computedHash = encode(rawPassword, credential.getHashIterations(), storedSalt);
    return computedHash.equals(credential.getValue());
}

private String encode(String rawPassword, int iterations, byte[] salt) {
    // Remove this hardcode once you're ready to use policy values
    iterations = 5000;
    byte[] rawBytes = rawPassword.getBytes(StandardCharsets.UTF_8);
    byte[] saltedBytes = concat(rawBytes, salt);
    byte[] digest = getSha512(saltedBytes);
    
    for (int i = 1; i < iterations; i++) {
        digest = getSha512(concat(digest, saltedBytes));
    }
    return Base64.getEncoder().encodeToString(digest);
}

@Override
public PasswordCredentialModel encodedCredential(String rawPassword, int iterations) {
    iterations = 5000;
    byte[] salt = generateSalt();
    System.out.println("New salt (Base64): " + Base64.getEncoder().encodeToString(salt));
    String encodedPassword = encode(rawPassword, iterations, salt);
    return PasswordCredentialModel.createFromValues(providerId, salt, iterations, encodedPassword);
}

These changes will fix your salt display confusion and ensure your password verification works consistently.

内容的提问来源于stack exchange,提问作者jeremy.raf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 06:52:30