Search code examples
javaspringsecurityshirosalt-cryptography

Having trouble with apache shiro saltedauthentication.hashProvidedCredentials not given expected hash


This is the second time I'm using apache shiro in a project but the first time am salting the password.this time around i use apache shiro 1.2.0 . I'm using shiro in a web application using jsp, spring, JPA(spring-data-jpa) and using SHA256 for encryption then base64 before saving to database. I have a SaltedJPARealm, a Sha256CredentialMatcher which implements a HashedCredentialMatcher. this is how i do

creating a user in my controller

RandomNumberGenerator rng = new SecureRandomNumberGenerator();
ByteSource salt = rng.nextBytes(10);              
        String hashedPasswordBase64 = new Sha256Hash(signupForm.getPassword(),salt).toBase64();

userService.createUser(signupForm.getFullName(), signupForm.getEmail(), hashedPasswordBase64, salt.toBase64());

so supposed my password is password1234 and the generated salt is /ZFfGOcSxYhy+g== so in my database i have password: whb+0AihIGJ4n8QwULj1tR6qSwCrA+1BUvnoe4q4Cy4= the salt in the salt field in the database is the same.

In my configuration in spring is:

<!--....-->
 <bean id="saltedJPARealm" class="bla.bla.webapp.security.SaltedJPARealm">
    <constructor-arg  ref="credMatcher"/>
</bean>

    <bean id="credMatcher" class="bla.bla.webapp.security.Sha256CredentialMatcher">
        <property name="storedCredentialsHexEncoded" value="false" />
        <property name="hashAlgorithmName" value="SHA-256" />
        <!--<property name="hashIterations" value="1024" />-->
    </bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" depends-on="userRepository">
    <property name="realm" ref="saltedJPARealm" />
</bean>
<!--....-->

login user

 Subject currentUser = SecurityUtils.getSubject();
 if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken(loginForm.getEmail(), loginForm.getPassword(), loginForm.isRememberMe());
  SecurityUtils.getSubject().login(token);
        }

The SaltedJPARealm's doGetAuthenticationInfo(AuthenticationToken at) returns SaltedAuthenticationInfo after getting the user from the database :

ByteSource salt = ByteSource.Util.bytes(user.getSalt());           
return new SimpleAuthenticationInfo(user, user.getPassword().toCharArray(),salt,this.getName());

the doCredentialsMatch of Sha256CredentialMatcher looks like :

    Object tokenfromSubmition = hashProvidedCredentials(token.getCredentials(),((SaltedAuthenticationInfo)info).getCredentialsSalt(),0);
    Object passwordFromStorage =this.getCredentials(info);

    Boolean match = equals(tokenfromSubmition, passwordFromStorage);
    return match;

full code is available here on pastie the authentication fails with this. but when i change the code not to salt the password(when creating account) and return AuthenticationInfo as opposed to SaltedAuthenticationInfo. it works with the same class. am wondering what exactly am doing wrong?


Solution

  • The PasswordService is a POJO (with nested properties) and its nested properties can be configured just as well with Spring:

    <bean id="passwordService" class="org.apache.shiro.authc.credential.DefaultPasswordService">
      <property name="hashService.hashAlgorithmName" value="SHA-512"/>
      <property name="hashService.hashIterations" value="500000"/>
    </bean>
    
    <bean id="myRealm" class="...">
      <property name="credentialsMatcher">
        <bean class="org.apache.shiro.authc.credential.PasswordMatcher">
          <property name="passwordService" ref="passwordService"/>
        </bean>
      </property>
    </bean>
    

    This allows the myRealm instance to use the PasswordService for credentials checking during a login attempt.

    To use the PasswordService to encrypt passwords when the end user sets their password (i.e. during account registration or password reset), you can inject the PasswordService bean and then use it:

    String encryptedPassword = passwordService.encryptPassword(signupForm.getPassword());
    
    userService.createUser(signupForm.getFullName(), signupForm.getEmail(), encryptedPassword);
    

    I think you'll find the Spring config and code usage much nicer/cleaner to use than the lower-level random number generators and HashService + Hash APIs.

    HTH!