1
votes

I have an old application running in PHP that uses the function base64_encode(hash_hmac(“sha512”, $p_password, $p_salt, true)) to encode passwords in our database. I am migrating this application to Java Spring Boot and want to encode the passwords during authentification in the exact same way.

I have found how to make the same hashing method with Java in this post Compute HMAC-SHA512 with secret key in java 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

But I still cannot find an example of how I can integrate this hasing method in Spring authentication process. I have to create a PasswordEncoder bean and I don't know what to put inside. I tried Pbkdf2PasswordEncoder because it can make some SHA-512 hash like in my app but I get the error Detected a Non-hex character at 1 or 2 position. It is probably due to the fact that the passwords are not prefixed by {pbkdf2} in the database. The following code is what I am currently using as PasswordEncoder

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

I need help to set up the right password encoder to use HMAC-SHA512 in my authentification process with Java Spring and in a second time, combine it with BCrytPasswordEncoder (for new users) with DelegatingPasswordEncoder. Maybe it requires to update the passwords in DB to prefix them with the right encoder ?

If my question is not accurate enough or missing information, please ask me for more details :)

2

2 Answers

0
votes

You need to add a DelegatingPasswordEncoder to your project configuration file. The DelegatingPasswordEncoder acts as a PasswordEncoder, and we use it when we have to choose from a collection of implementations.:

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

With

return new DelegatingPasswordEncoder("pbkdf2", encoders); 

we are saying to Spring Security: "Use the 'pbkdf2' as default password encoder".

If the provided hash is {scrypt}12345, the DelegatingPasswordEncoder delegates to the SCryptPasswordEncoder, if there is no prefix, the application will use default one.

0
votes

I finally got what I wanted. I created an implementation of PasswordEncoder inspired by https://github.com/lathspell/java_test/blob/master/java_test_openldap/src/main/java/LdapSha512PasswordEncoder.java

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

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