如何在 Spring 引导安全中使用 HMAC-SHA512 对密码进行编码

How to encode password with HMAC-SHA512 in Spring Boot Security

我在 PHP 中有一个旧应用程序 运行,它使用函数 base64_encode(hash_hmac(“sha512”, $p_password, $p_salt, true)) 对我们数据库中的密码进行编码。 我正在将此应用程序迁移到 Java Spring 引导,并希望在身份验证期间以完全相同的方式对密码进行编码。

我在这个 post and I also learnt that we can have several password encoders for old and new users with https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#constructing-delegatingpasswordencoder

中找到了如何使用 Java 制作相同的散列方法

但我仍然找不到如何在 Spring 身份验证过程中集成此 hasing 方法的示例。我必须创建一个 PasswordEncoder bean,但我不知道要在里面放什么。 我尝试了 Pbkdf2PasswordEncoder,因为它可以像我的应用程序一样生成一些 SHA-512 哈希,但我收到错误 Detected a Non-hex character at 1 or 2 position。 这可能是由于数据库中的密码没有以 {pbkdf2} 为前缀。以下代码是我目前使用的 PasswordEncoder

@Bean
public PasswordEncoder passwordEncoder() {
     Pbkdf2PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder("salt");
     passwordEncoder.setAlgorithm(Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA512);
     return passwordEncoder;
}

我需要帮助来设置正确的密码编码器以在我的身份验证过程中使用 HMAC-SHA512 Java Spring 并在第二次, 将它与 BCrytPasswordEncoder(对于新用户)和 DelegatingPasswordEncoder 结合起来。 也许它需要更新数据库中的密码以使用正确的编码器作为前缀?

如果我的问题不够准确或缺少信息,请向我询问更多详情:)

您需要将 DelegatingPasswordEncoder 添加到您的项目配置文件中。 DelegatingPasswordEncoder 充当 PasswordEncoder,当我们必须从实现集合中进行选择时,我们会使用它。:

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {       
    
  @Bean
  public PasswordEncoder passwordEncoder() {
      Map<String, PasswordEncoder> encoders = new HashMap<>();  
    
    Pbkdf2PasswordEncoder bcryprPe = new Pbkdf2PasswordEncoder("salt");       
    bcryprPe.setAlgorithm(
       Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA512);
    encoders.put("pbkdf2", pbkdf2Pe);
    // add other PasswordEncoder here: 
    encoders.put("scrypt", new SCryptPasswordEncoder());
      return new DelegatingPasswordEncoder("pbkdf2", encoders);
  }
}

return new DelegatingPasswordEncoder("pbkdf2", encoders); 

我们对 Spring 安全人员说:“使用 'pbkdf2' 作为默认密码编码器”。

如果提供的哈希值是 {scrypt}12345,DelegatingPasswordEncoder 委托给 SCryptPasswordEncoder,如果没有前缀,应用程序将使用默认值。

我终于如愿以偿了。我创建了一个受 https://github.com/lathspell/java_test/blob/master/java_test_openldap/src/main/java/LdapSha512PasswordEncoder.java

启发的 PasswordEncoder 实现

WebSecurityConfig.java

@Bean
public PasswordEncoder passwordEncoder() {
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put("SSHA-512", new Hmac512PasswordEncoder("salt"));
        encoders.put("bcrypt", new BCryptPasswordEncoder());
        return new DelegatingPasswordEncoder("SSHA-512", encoders);
}

Hmac512PasswordEncoder.java

public class Hmac512PasswordEncoder implements PasswordEncoder {

private static final String SSHA512_PREFIX = "{SSHA-512}";
private static final String HMAC_SHA512 = "HmacSHA512";

private final String salt;

public Hmac512PasswordEncoder(String salt) {
    if (salt == null) {
        throw new IllegalArgumentException("salt cannot be null");
    }
    this.salt = salt;
}

public String encode(CharSequence rawPassword) {
    String result = null;

    try {
        Mac sha512Hmac = Mac.getInstance(HMAC_SHA512);
        final byte[] byteKey = Utf8.encode(salt);
        SecretKeySpec keySpec = new SecretKeySpec(byteKey, HMAC_SHA512);
        sha512Hmac.init(keySpec);
        byte[] macData = sha512Hmac.doFinal(Utf8.encode(rawPassword.toString()));

        result = SSHA512_PREFIX + Base64.getEncoder().encodeToString(macData);
        //result = bytesToHex(macData);
    } catch (InvalidKeyException | NoSuchAlgorithmException e) {
        e.printStackTrace();
    }

    return result;
}

public boolean matches(CharSequence rawPassword, String encodedPassword) {
    if (rawPassword == null || encodedPassword == null) {
        return false;
    }

    String encodedRawPass = encode(rawPassword);

    return MessageDigest.isEqual(Utf8.encode(encodedRawPass), Utf8.encode(encodedPassword));
}
}