spring-bootspring-securityshiro

Migrating Shiro hashed passwords to Spring Security hashed passwords


Spring Boot 2.6.2 web application...

Looking at migrating all shiro hashed passwords to spring security hashed passwords (using Pbkdf2PasswordEncoder)

So far, what I'm looking at is reconstructing the shiro hash into proper format for spring password encoder to match successfully.

Taking a sample shiro hash:

$shiro1$SHA-512$50000$wnjBM5pWpwN5784Aq7i7QA==$IqJ6XdwuKtCNqJCu982CzR6b4A3lnGp2F/WD8tmwVC7SAnOgeuUgtBbJ/ki9FqiIbX/ngF9RJd+5iy971d88cg==

In my custom user details service loadUserByUsername method, I split the password hash string on $ character, and re-assemble it so that on first login, Spring can properly match the hashes.

What's unclear is the format Spring uses in the password hash to denote the different parts...

Note: my shiro implementation was using a private salt, along with randomized salts, 50000 iterations SHA-512..

Code sample from custom User details service:

String[] pwdParts = user.getPassword().split("\\$");
String privateSalt = "myprivatesalt";

    UserDetails coreUser = User.withUsername(username)
            .password(privateSalt + pwdParts[3] + pwdParts[4])
            .roles(roles.toArray(new String[0])).authorities(permissions.toArray(new String[0]))
            .accountLocked(user.isLocked()).build();

    CurrentUser currentUser = new CurrentUser(username, coreUser.getPassword(), coreUser.getAuthorities());

Am I on the right track here? Currently, Spring fails on login in the encode:

@Bean
public PasswordEncoder encoder() {
    Pbkdf2PasswordEncoder pe = new Pbkdf2PasswordEncoder(myPrivateSalt, 50000, 512);
    pe.setAlgorithm(SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA512);
    return pe;
}

My goal is to avoid having users resetting their passwords, and hopefully silently update their passwords in the background as they login for the first time on the new version of our application.


Solution

  • The reason is that Spring Security does not include the algorithm and rounds in the hashed value.

    You can keep the same hash by implementing your own encoder like so:

    public class ShiroSpringPbkdf2PasswordEncoder implements PasswordEncoder {
    
        private static final SHIRO_PREFIX = "$shiro1$SHA-512$50000$";
    
        private final Pbkdf2PasswordEncoder encoder =
            new Pbkdf2PasswordEncoder(myPrivateSalt, 50000, 512);
    
        public String encode(CharSequence rawPassword) {
            return SHIRO_PREFIX + this.encoder.encode(rawPassword);
        }
    
        public boolean matches(CharSequence rawPassword, String encodedPassword)     {
            if (encodedPassword.startsWith(SHIRO_PREFIX)) {
                return this.encoder.matches(rawPassword, encodedPassword.substring(SHIRO_PREFIX.length()));
            }
            return this.encoder.matches(rawPassword, encodedPassword);
        }
    }
    

    (Because you also have an additional salt, there is some extra string manipulation to do in addition to the above.)

    Regarding getting passwords migrated, you can publish implement PasswordEncoder#upgradeEncoding to look at the encoded value and decide if it needs to change and publish a UserDetailsPasswordService that does the actual upgrading.

    Since Spring Security does not currently include the algorithm and rounds in its PBKDF2 hash yet, there may be some value in you leaving the hashes the way that they are. Once Spring Security does support that, it would be a better time to migrate the format.