I have a Spring boot 2.0.1 service to which I added Basic authentication which uses BCrypt for hashing. But this service which used to give an average of 400 ms before adding Basic auth is now taking more than 1 second. I am using User details service which looks up the sent user name in a hash map and returns UserDetails. I tried reducing BCrypt rounds down to 4 but that didn't make much of a difference.
Earlier I had stateless authentication enabled which I later disabled but again performance stayed bad. This service is hosted in a Docker container.
Below is my Security config.
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
@Autowired
public SecurityConfig(UserDetailsServiceImpl service) {
this.userDetailsService = service;
}
@Bean
public PasswordEncoder passwordEncoder() {
Map encoders = new HashMap<>();
encoders.put(BCRYPT_ID, new BCryptPasswordEncoder(BCRYPT_ROUNDS));
return new DelegatingPasswordEncoder(BCRYPT_ID,encoders);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.httpBasic();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
}
Please let me know if I am missing something.
Update: I ran benchmarks and it looks like BCrypt encoder is making the application slow. I found some Stack Overflow answers discussing that BCrypt hash calculation is a blocking call.
About hardware: The service host machine has Intel Xeon E5, 16 GB memory. It hosts 4 Spring boot Services each assigned 2 GB running inside a Docker container.
So after searching lot about BCrypt encoding and decoding , I have finally found the solution to maintain the performance of spring boot project without major delays caused by BCrypt encoding and decoding.
So BCrypt hashing algorithm works on certain rounds. More the number of rounds you use in your BCrypt encoding, more the space and memory your project will consume to do the encoding as well as decoding (password).
Having said that, I would also like to mention, more the number of rounds you use while encoding your password, more secure it will be.
Most of us would have used these below lines of code to generate the bycrypt credentials in our code
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16); // here no of rounds is 16
String result = encoder.encode("password");
system.out.println("encoded password" + result );
The number of rounds that this java code used for generating the BCrypt encoded password is 16 which is too high. the standard round number that can help to serve the balance between time, memory and security is 10.
so if we have to change the number of rounds in the BCrypt Encoding below is what you need to set
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
When I used 16 rounds, it took me 6 sec to hit the service with basic authentication (3 to 4 sec on average for encoding and decoding BCrypt password)
Where as when I used 10 rounds, I was on able to hit the service with in an average time between 1 and 1.5 sec
There is no standard no of rounds that you should opt for. You should use the maximum number of rounds which is tolerable, performance-wise, in your application. The number of rounds is a slowdown factor, which you use on the basis that under normal usage conditions, such a slowdown has negligible impact for you (the user will not see it, the extra CPU cost does not imply buying a bigger server, and so on). This heavily depends on the operational context: what machines are involved, how many user authentications per second... so there is no one-size-fits-all response.
Some time ago I had the same issue with a server while working on a project for my customer. I analysed the service with jvisualvm. The results were really clear:
I used jmeter to fire 5000 requests against my service's api. I also restarted the service for every measurement and always run a 5000 samples to warm up the JIT compiler before every measurement.
My results were this:
Samples | avg [ms]| min [ms]| max [ms]| throughput [requests/s]
------------+------------+------------+------------+------------------------
5000 | 125| 71| 363| 78.1
------------+------------+------------+------------+------------------------
The service I analyses was a very simple data access service that just provided access to a database through a REST interface. Therefore I could find out how big the impact of bcrypt was with my jmeter tests.
I then run multiple jmeter tests with different password encoders like MD5, SHA-256, bcrypt, scrypt and pbkdf2.
Here are some of my measurements that might help you make a decision or for further investigation:
algorithm | avg [ms]| min [ms]| max [ms]| throughput [requests/s]
------------+------------+------------+------------+------------------------
MD5 | 5| 1| 61| 1443
------------+------------+------------+------------+------------------------
SHA-256 | 5| 2| 34| 1464
------------+------------+------------+------------+------------------------
bcrypt | 125| 71| 363| 78.1
------------+------------+------------+------------+------------------------
scrypt | 122| 54| 1232| 79.2
------------+------------+------------+------------+------------------------
pbkdf2 | 833| 421| 1606| 12
------------+------------+------------+------------+------------------------
The service that I analysed is not exposed to the internet. It runs in a safe network zone and performance was more critical then security. So we decided to use a SHA-256 instead of bcrypt.
I think that:
But you should always try to achieve 1.
The measurements I showed should be interpreted relativ and not be seen as absolut values.
PS: I know it would be better to execute performance tests that test the encryption api in isolation instead of testing them through a service's REST interface, but I didn't had time yet to setup that kind of test. Hopefully I will have more time to investigate it in the future and if so I will come back update this answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With