1

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 :)

TCH
  • 421
  • 1
  • 6
  • 25

2 Answers2

0

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.

Meziane
  • 1,586
  • 1
  • 12
  • 22
  • Hi, I tryied your solution even if I don't think this is my real need. I get following exception "There is no PasswordEncoder mapped for the id "null"" because I think the passwords in my database are not prefixed by {pbkdf2}. Actually, I am not sure Pbkdf2PasswordEncoder is the right encoder for me but I thought it was the closest one to reproduce the same result as my method base64_encode(hash_hmac(“sha512”, $p_password, $p_salt, true)) (in PHP). I just need a xxxPasswordEncoder configuration to reproduce that hasing method, but maybe it is not possible – TCH Mar 02 '21 at 15:17
  • To configure multiple **PasswordEncoder**, the use of **DelegatingPasswordEncoder** is definitly the way to do it. Now which **PasswordEncoder** to use is another question. Try in a first step to use _your_ **PasswordEncoder**, than use the **DelegatingPasswordEncoder** to use a second **PasswordEncoder**. – Meziane Mar 03 '21 at 10:46
  • I know how works DelegatingPasswordEncoder. My question is only about creating my own PasswordEncoder or use one from Spring security that does the same as base64_encode(hash_hmac(“sha512”, $p_password, $p_salt, true)) in PHP – TCH Mar 03 '21 at 13:44
  • You know how **DelegatingPasswordEncoder** works? That's not what I understood from your question. Any way, you seem to have solved your problem. That's the purpose of stackoverflow: I have just tried to help you. – Meziane Mar 03 '21 at 19:49
0

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));
}
}
TCH
  • 421
  • 1
  • 6
  • 25