How password hash algorithm for stateless basic authentication api?

Viewed 246

I have a spring webservice api that authenticates users from a properties file. The passwords are stored in bcrypt algorithm so far.

Problem: my api is stateless, thus any basic auth request will force bcrypt authentication to recalculate, causing delays of approx 100ms on each request.

Question: which algorithm is advised to be used for encrypting user passwords (no matter if in properties file or db), that are to be used on every request on a stateless api?

With focus on authentication performance, but not neglecting security.

2 Answers

I'd skip encrypting the password and use tokens instead. One such solution is JWT (Json Web Tokens) and Spring comes with support for them out of the box.

You can take a look at an example here.

You can go even further (this is what I do) and delegate all of this into an OAuth provider. I usually use Keycloak for this.

In the end I decided to create a caching authentication handler. It cashes the password from basic auth, and the belonging hashed bcrypt string. But only if authentication was successful.

This way I can speed up known clients with valid authentications (as I won't have to recalculate the bcrypt hash match for them), but still keep the brutforce benefits from bcrypt algorithm.

As follows the spring-specific implementation:

static class CachingDelegatingPasswordEncoder implements PasswordEncoder {
        private final PasswordEncoder delegate = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        private final Map<String, String> cache = new HashMap<>();

        @Override
        public String encode(CharSequence rawPassword) {
            return delegate.encode(rawPassword);
        }

        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            String cachedPassword = cache.get(rawPassword);
            if (cachedPassword != null && StringUtils.equals(cachedPassword, encodedPassword))
                return true;

            boolean match = delegate.matches(rawPassword, encodedPassword);
            if (match) cache.put(rawPassword.toString(), encodedPassword);

            return match;
        }
}

@Configuration
static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(AuthenticationManagerBuilder builder) throws Exception {
        DaoAuthenticationProvider p = new DaoAuthenticationProvider();
        p.setPasswordEncoder(new CachingDelegatingPasswordEncoder());
        p.setUserDetailsService(userDetailsService);
        p.afterPropertiesSet();
        builder.authenticationProvider(p);
    }
}
Related