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 ...}
Related
My objective is to hash a password that is passed from the command line argument and save it to the application.yaml. Then when the user authenticates using the password to match it and allow users in.
I am passing the value like this:
mvn spring-boot:run -Dspring-boot.run.arguments="--user.password=admin"
The fragment of the security section in application.yaml looks like this:
security:
user:
name: admin
password: ${user.password}
I have written some password encoder classes in my spring boot application.
class MyPasswordEncoder : PasswordEncoder {
private val logger = LoggerFactory.getLogger(javaClass)
private val argon2PasswordEncoder = Argon2PasswordEncoder(SALT_LENGTH, HASH_LENGTH, PARALLELISM, MEMORY_USE, ITERATIONS)
/**
* Encrypt the password (Algorithm Used: Argon2id)
*/
override fun encode(decryptedPassword: CharSequence?): String {
val hashedPassword : String = argon2PasswordEncoder.encode(decryptedPassword)
logger.debug("Password Encoding Successful!")
return hashedPassword
}
/**
* Matches the password with the encoded password (Algorithm Used: Argon2id)
*/
override fun matches(rawPassword: CharSequence?, encodedPassword: String?): Boolean {
return argon2PasswordEncoder.matches(rawPassword, encodedPassword)
}
}
And the Delegation class:
class MyPasswordDelegation {
fun createDelegatingPasswordEncoder(): PasswordEncoder {
val idForEncode = "myEncoder"
val encoders: MutableMap<String, PasswordEncoder> = mutableMapOf()
encoders[idForEncode] = NCaaSPasswordEncoder()
return DelegatingPasswordEncoder(idForEncode, encoders)
}
}
My Security Config class looks like this:
#Configuration
#EnableWebSecurity
class SecurityConfig {
#Value("\${spring.security.user.name}")
private val userName: String? = null
#Value("\${spring.security.user.password}")
private val password: String? = null
#Autowired
lateinit var appAuthenticationEntryPoint: AppAuthenticationEntryPoint
#Bean
fun passwordEncoder(): MyPasswordEncoder {
return MyPasswordEncoder()
}
#Bean
#Throws(Exception::class)
fun userDetailsService(): InMemoryUserDetailsManager? {
val userDetails : UserDetails = User.withUsername(userName).password(passwordEncoder().encode(password).roles("USER").build()
return InMemoryUserDetailsManager(userDetails)
}
#Throws(Exception::class)
#Bean
fun filterChain(httpSecurity : HttpSecurity): SecurityFilterChain {
httpSecurity.csrf().disable()
// Allow only HTTPS Requests
httpSecurity.requiresChannel {
channel -> channel.anyRequest().requiresSecure()
}.authorizeRequests {
authorize -> authorize.anyRequest().fullyAuthenticated().and().httpBasic().and().exceptionHandling().authenticationEntryPoint(appAuthenticationEntryPoint)
}
return httpSecurity.build()
}
}
But when I try to authenticate using admin/admin, I get the following error:
Failed to process authentication request
org.springframework.security.authentication.BadCredentialsException: Bad credentials
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:79)
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:147)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201)
at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:172)
I know I am going wrong somewhere. But since this is all new to me, I am not able to quite put it all together.
Any help would be appreciated. Thanks!
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.
I am using spring boot:2.2.2.RELEASE when i tried to add jasypt functionality to hide my password i got the following error
Unable to decrypt: ENC(MyEncryptedPass). Decryption of Properties failed, make sure encryption/decryption passwords match
i used the command line to encrypt the password and decrypt it and it works fine so i am sure my encryption and decryption passwords are exact but i get this error when i try to launch my spring application. So any help (•–•)
As from version 3.0.0 of jasypt-spring-boot, the default encryption/decryption algorithm has changed to PBEWITHHMACSHA512ANDAES_256
The change can be found here: https://github.com/ulisesbocchio/jasypt-spring-boot#update-11242019-version-300-release-includes
To decrypt previously encrypted values, add these two values in your properties:
jasypt.encryptor.algorithm=PBEWithMD5AndDES
jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator
I was also facing the same issue. Initially, I was encrypting using jasypt CLI and putting the same value in the property file. But by default property of com.github.ulisesbocchio jar is different from CLI. Try to use the below code for encryption.
private static StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(password);
config.setAlgorithm("PBEWithMD5AndDES");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
private static String encrypt(String text) {
StringEncryptor textEncryptor = stringEncryptor();
String encryptedText = textEncryptor.encrypt(text);
return encryptedText;
}
private static String decrypt(String text) {
StringEncryptor textEncryptor = stringEncryptor();
String decryptedText = textEncryptor.decrypt(text);
return decryptedText;
}
public static void main(String[] args) {
System.out.println(encrypt("StackOverFlow"));
}
Ok the solution was to add this configuration to my project.properties file
jasypt.encryptor.iv-generator-classname=org.jasypt.iv.NoIvGenerator
cause probably i am using algorithm = PBEWithMD5AndDES which doesn't require initialization vector. But of course that's just my explanation which doesn't mean anything :''D.
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 ?
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