BCryptPasswordEncoder affects startup time of server - spring-boot

Why does the strength of the BCryptPasswordEncoder affect the startup time of the server? There are no hashes generated at startup, so I'm wondering why this does have any effect on the startup.
Of course, I understand that checking whether a password matches takes time, but at start up is strange.
Code looks like this:
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(17); // Affects startup time tremendously
}
#Autowired
BCryptPasswordEncoder bcryptEncoder;
#Autowired
CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(bcryptEncoder);
}

(Depending on your configuration)
Have a look at the spring DaoAuthenticationProvider
The following method is called at startup:
private void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.userNotFoundEncodedPassword = passwordEncoder.encodePassword(
USER_NOT_FOUND_PASSWORD, null);
this.passwordEncoder = passwordEncoder;
}
This was introduced so the server has an encoded password to verify the password against from a username attempting to authenticate doesn't exist.
/**
* The plaintext password used to perform
* {#link PasswordEncoder#isPasswordValid(String, String, Object)} on when the user is
* not found to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
Refer to:
https://github.com/spring-projects/spring-security/issues/2280
https://jira.spring.io/browse/SEC-2056
Note: If you set the strength to 17 and your server is taking an extra 5minutes to start, it will take your sever approximately 5minutes to verify each users password when they authenticate.

This issue is now resolved, so upgrade your Spring Boot/Spring Security if necessary.
Spring Security calls PasswordEncoder.matches() regardless of whether the user was found, so that hackers can't detect if the user existed or not by comparing response times. To create the dummy encrypted password or hash for non-existing users, Spring Security calls PasswordEncoder.encode("userNotFoundEncodedPassword") once and reuses the result. This call was previously made during startup but is now called lazily when first needed.

The constructor of BCryptPasswordEncoder is not doing anything on startup depending on the password strength :
public BCryptPasswordEncoder(int strength) {
this(strength, null);
}
public BCryptPasswordEncoder(int strength, SecureRandom random) {
if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
throw new IllegalArgumentException("Bad strength");
}
this.strength = strength;
this.random = random;
}
Having seen this, I dont think that only changing the strength parameter can increase startup time as described.
But when you actually use the encrypter, 'strength' will for sure impact the performance. So may be you are encrypting many passwords somewhere at startup ?

Related

Spring Boot Password Encoding

I am new to Spring boot and I am trying to configure the security for my api. I am using PasswordEncoding:
public static String encodePassword(String plainPassword){
BCryptPasswordEncoder bCryptPasswordEncoder=new BCryptPasswordEncoder();
return bCryptPasswordEncoder.encode(plainPassword);
}
In the SecurityConfig class I got the following method:
#Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
But each time given the same input the output is always different, can someone explain to me the reason behind this and how I can possibly fix this?
This is by design, there's nothing for you to "fix". The reason is because the BCrypt algorithm includes a salt, which will be different every time you call it. What this means is that if you're trying to encode a plain-text password to a hash and compare it to another hash, it's not going to match. You can, however, use the method, matches, in BCryptPasswordEncoder to compare.
Here's a test that demonstrates this
#Test
public void encodeAndMatch() {
BCryptPasswordEncoder bc = new BCryptPasswordEncoder();
String p1 = bc.encode("password");
String p2 = bc.encode("password");
String p3 = bc.encode("password");
assertNotEquals(p1, p2);
assertNotEquals(p1, p3);
assertNotEquals(p2, p3);
assertTrue(bc.matches("password", p1));
assertTrue(bc.matches("password", p2));
assertTrue(bc.matches("password", p3));
}
Here you can see that the same password generated three distinct hashes, but the encoder can still compare the original plain-text password to each of them and match.

(Redis, Springboot) Although TTL remains but expired event is generated

I have been developing some program with Spring and Redis but I am in stuck :(
What I want
when http request comes to server, then setex myKey, value and ttl.
when myKey is expired, then expired event comes to server and I will make a log.
Source code
#Configuration
public class RedisListenerContainerConfig {
#Bean("redisMyListenerContainer")
public RedisMessageListenerContainer redisLPRInOutCarExpiryListenerContainer(
RedisConnectionFactory redisConnectionFactory,
RedisLPRInOutCarExpiryListener redisLPRInOutCarExpiryListener) {
redisConnectionFactory.getConnection().setConfig("notify-keyspace-events", "Kx");
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
container.addMessageListener(myListener, new PatternTopic("__keyspace#0__:myKey*"));
container.setErrorHandler(e -> log.error("error"));
return container;
}
}
// here expired event will be got
#Component
public class MyListener implements MessageListener {
#Override
public void onMessage(Message message, byte[] pattern) {
// to make log
}
}
#Service
public class MyService {
...
// here setex is generated
private void setExMyKey(String myKey) {
String value = "value";
redisTemplate.opsForValue().set(myKey, value, 60 * 10 ,TimeUnit.SECONDS);
final String listKey = "list_" + myKey;
if (redisTemplate.opsForList().size(listKey) < 1) {
redisTemplate.opsForList().leftPush(listKey, value);
}
}
}
info of Redis
standalone
keys: 610000
expires: 600000
Problem
Actually, the http requests keep coming to the server so it supposes to the ttl of mykey will never be zero.
But after taking a while, although ttl still remains, expired event is generated so many times.
127.0.0.1:6379> ttl myKey_3378
(integer) 1783
the suspicious part
According to Redis documents, I think, my function for setex does not change the value but only ttl.
And that could mean myKey is not really modified.
IMPORTANT all the commands generate events only if the target key is really modified.
For instance an SREM deleting a non-existing element from a Set will not actually change the value of the key, so no event will be generated.
Is it possible to be a issue of Redis lock?
My language is not English so I am sorry in advance not to understand very well.
Hopefully I solve this problem soon.
Thank you so much.

how to compare a password text with the bcrypt hashes?

I have a use case in my application that should prevent the user from choosing one of their last 3 passwords while resetting their password. I'm using Angular for the front end and Spring Boot for the back end . In my scenario, the user passwords are stored as bcrypt hash.
How can I compare the password entered by the user with the last 3 stored bcrypt passwords?
When I run the following code snipped example,
BCryptPasswordEncoder b = new BCryptPasswordEncoder();
for(int i =0;i<10;i++) {
System.out.println(b.encode("passw0rd"));
}
it generates the following bcrypt hashes. each hash is different which is reasonable because when I check the org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder, I can see the salt generated is random value.
$2a$10$tztZsPFZ.T.82Gl/VIuMt.RDjayTwuMLAkRkO9SB.rd92vHWKZmRm
$2a$10$yTHyWDmcCBq3OSPOxjj4TuW9qXYE31CU.fFlWxppii9AizL0lKMzO
$2a$10$Z6aVwg.FNq/2I4zmDjDOceT9ha0Ur/UKsCfdADLvNHiZpR7Sz53fC
$2a$10$yKDVeOUvfTQuTnCHGJp.LeURFcXK6JcHB6lrSgoX1pRjxXDoc8up.
$2a$10$ZuAL06GS7shHz.U/ywb2iuhv2Spubl7Xo4NZ7QOYw3cHWK7/7ZKcC
$2a$10$4T37YehBTmPWuN9j.ga2XeF9GHy6EWDhQS5Uc9bHvJTK8.xIm1coS
$2a$10$o/zxjGkArT7YdDkrk5Qer.oJbZAYpJW39iWAWFqbOhpTf3FmyfWRC
$2a$10$eo7yuuE2f7XqJL8Wjyz.F.xj78ltWuMS1P0O/I6X7iNPwdsWMVzu6
$2a$10$3ErH2GtZpYJGg1BhfgcO/uOt/L2wYg4RoO8.fNRam458WWdymdQLW
$2a$10$IksOJvL/a0ebl4R2/nbMQ.XmjNARIzNo8.aLXiTFs1Pxd06SsnOWa
Spring security configuration.
#Configuration
#Import(SecurityProblemSupport.class)
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#PostConstruct
public void init() {
try {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
} catch (Exception e) {
throw new BeanInitializationException("Security configuration failed", e);
}
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
you can use matches method in BCryptPasswordEncoder, something like this:
b.matches("passw0rd", hash)
Actually I found my answer .
I realized that I can use matches function in the class org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.
System.out.println(b.matches("passw0rd", "$2a$10$tztZsPFZ.T.82Gl/VIuMt.RDjayTwuMLAkRkO9SB.rd92vHWKZmRm"));
Spring Security just reads the salt from previously generated hash and rehashes the input password again with same salt. And it compares both final hashes and obviously it will be same.
Example:
Password: test
Hash: $2a$10$nCgoWdqJwQs9prt7X5a/2eWLn88I8pon6iNat90u4rq4mHqtoPGQy
Hash has 3 segments each separated by $ symbol. 2a is version of the Bcrypt, 10 is the total rounds and nCgoWdqJwQs9prt7X5a/2e is the salt.
So spring security takes the password test and salt nCgoWdqJwQs9prt7X5a/2e and runs the hashing method. Obviously it generates the same hash as the password and salt matches.
Try the below :
BCryptPasswordEncoder bc = new BCryptPasswordEncoder();
boolean passChecker = bc.matches("Normal Password Here", "Hashed Password Here");
I had been facing a scenario where I had to verify my old password which is stored as bcrypted into DB in order to Change the password.
then I did it this way.
BCryptPasswordEncoder b = new BCryptPasswordEncoder();
if(b.matches(oldNormalPassword, #Password)){ // code ...}

Test sending email in Spring

I want to test my services in spring which should send emails.
I try to use org.subethamail:subethasmtp.
To acieve my goal I created service MySender where I send email:
#Autowired
private MailSender mailSender;
//...
SimpleMailMessage message = new SimpleMailMessage();
message.setTo("example#example.com");
message.setSubject("Subject");
message.setText("Text");
mailSender.send(message);
// ...
To test this piece of code I created test application.properties (in test scope):
spring.mail.host=127.0.0.1
spring.mail.port=${random.int[4000,6000]}
And test configuration class which should start Wiser SMTP server and make it reusable in tests:
#Configuration
public class TestConfiguration {
#Autowired
private Wiser wiser;
#Value("${spring.mail.host}")
String smtpHost;
#Value("${spring.mail.port}")
int smtpPort;
#Bean
public Wiser provideWiser() {
// provide wiser for verification in tests
Wiser wiser = new Wiser();
return wiser;
}
#PostConstruct
public void initializeMailServer() {
// start server
wiser.setHostname(smtpHost);
wiser.setPort(smtpPort);
wiser.start();
}
#PreDestroy
public void shutdownMailServer() {
// stop server
wiser.stop();
}
}
Expected result is that application sends email using Wiser smtp server and verify number of sended messages.
But when I run service application throws MailSendException(Couldn't connect to host, port: 127.0.0.1, 4688; timeout -1;).
But when I add breakpoint and try connect using telnet smtp server allow to connect and don't throw Connection refused.
Do you have any idea why I can't test sending mails?
Full code preview is available on github:
https://github.com/karolrynio/demo-mail
I faced same problem. If using some constant port number for spring.mail.port in test Spring configuration combined with Maven tests forking, it resulted in tests randomly failing on port conflict when starting Wiser.
As noted here in comments, using random.int doesn't help - it returns different value each time it's referenced, and it's expected behavior (see this issue).
Hence, we need a different way to initialize spring.mail.port with a random value, so it would be constant within the test execution. Here's a way to do it (thanks for advice here):
First, we may not set spring.mail.port in test properties file at all. We'll initialize it in TestPropertySource. We'll need a class like this:
public class RandomPortInitailizer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
int randomPort = SocketUtils.findAvailableTcpPort();
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext,
"spring.mail.port=" + randomPort);
}
}
Now we can run our tests this way (not too different from what's found in OP):
#RunWith(SpringRunner.class)
#ContextConfiguration(initializers = RandomPortInitailizer.class)
public class WhenEmailingSomeStuff {
#Value("${spring.mail.host}")
String smtpHost;
#Value("${spring.mail.port}")
int smtpPort;
#Before
public void startEmailServer() {
wiser = new Wiser();
wiser.setPort(smtpPort);
wiser.setHostname(smtpHost);
wiser.start();
}
#After
public void stopEmailServer() {
wiser.stop();
}
#Test
public void testYourJavaMailSenderHere() {
//
}
}
in the application properties can you also add
mail.smtp.auth=false
mail.smtp.starttls.enable=false
The change your code to have these extra two values
#Value("${mail.smtp.auth}")
private boolean auth;
#Value("${mail.smtp.starttls.enable}")
private boolean starttls;
and put these options in your initializeMailServer
Properties mailProperties = new Properties();
mailProperties.put("mail.smtp.auth", auth);
mailProperties.put("mail.smtp.starttls.enable", starttls);
wiser.setJavaMailProperties(mailProperties);
wiser.setHostname(smtpHost);
wiser.setPort(smtpPort);
wiser.start();
let me know if this worked for you

Spring security jdbcAuthentication does not work with default roles processing

Using
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("dba").password("root123").roles("ADMIN","DBA");
my example works fine. For example for
http.authorizeRequests()
// ...
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.and().formLogin()
.and().exceptionHandling().accessDeniedPage("/Access_Denied");
If I have changed inMemoryAuthentication to spring jdbc default - i got an role issue than.
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
I sure I configured db and schema using spring recommendations (to be able to use default jdbc authentication).
In debug mode I can see result of loading from db in the
org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl
#loadUserByUsername(username)[line 208]
return createUserDetails(username, user, dbAuths);
It returns similar result with in memory configuration:
org.springframework.security.core.userdetails.User#183a3:
Username: dba;
Password: [PROTECTED];
Enabled: true;
AccountNonExpired: true;
credentialsNonExpired: true;
AccountNonLocked: true;
Granted Authorities: ADMIN,DBA
As you can see it loads correspond Granted Authorities, but http request redirects me to .accessDeniedPage("/Access_Denied"). I confused because It should work for user like time before.
I do not use spring boot in my project.
My logs does not contain any configuration of jdbc errors.
I have spend a lot of time to investigate details and my ideas have just finished.
Do you think I need add to build some cache libraries or something else?
There are 2 gotchas in play here.
The first is that when using hasRole('ADMIN') that first a check is done if it starts with the role prefix (for which the default is ROLE_) if not the passed in role is prefix with it (see also the reference guide). So in this case the actual authority checked is ROLE_ADMIN and not ADMIN as you expect/assume.
The second is that when using the in memory option the roles method does the same as mentioned here. It checks if the passed in roles start with the role prefix and if not adds it. So in your sample with the in memory one you end up with authorities ROLE_ADMIN and ROLE_DBA.
However in your JDBC option you have authorities ADMIN and DBA and hence the hasRole('ADMIN') check fails because ROLE_ADMIN isn't equal to ADMIN.
To fix you have several options.
Instead of hasRole use hasAuthority the latter doesn't add the role prefix and for the in memory option use authorities instead of roles.
In the JDBC option prefix the authorities in the database with ROLE_
Set the default role prefix to empty.
Using hasAuthority
First change the configuration of the in memory database to use authorities instead of roles.
auth.inMemoryAuthentication()
.withUser("dba").password("root123")
.authorities("ADMIN","DBA");
next change your expressions as well
.antMatchers("/db/**").access("hasAuthority('ADMIN') and hasAuthority('DBA')")
Prefix with ROLE_
In the script that inserts the authorities prefix the authorities with ROLE_.
Remove the default role prefix
This is a bit tricky and is extensivly described in [the migration guide].
There is no easy configuration option and requires a BeanPostProcessor.
public class DefaultRolesPrefixPostProcessor implements BeanPostProcessor, PriorityOrdered {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
// remove this if you are not using JSR-250
if(bean instanceof Jsr250MethodSecurityMetadataSource) {
((Jsr250MethodSecurityMetadataSource) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultMethodSecurityExpressionHandler) {
((DefaultMethodSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultWebSecurityExpressionHandler) {
((DefaultWebSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
if(bean instanceof SecurityContextHolderAwareRequestFilter) {
((SecurityContextHolderAwareRequestFilter)bean).setRolePrefix("");
}
return bean;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
#Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
}
You can see see what happened enabling the logging. In your application.properties add:
# ==============================================================
# = Logging springframework
# ==============================================================
logging.level.org.springframework.jdbc=DEBUG
logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.http=DEBUG

Resources