Use Bcrypt instead of SHA with Apache Shiro in Java ☕

1. Overview

While Apache works great with SHA 256 hashes there are scenarios where one would like to like to use different hash functions like bcrypt.

One reason is that bcrypt is said to be harder to attack with current equipment than SHA 256 and thus has widely been adopted. This leads to the second reason for using it with Apache Shiro: if you have already bcrypt password hashes in your database and don’t want existing users to rehash their password.

2. Adapting to bcrypt from SHA

The following steps assume that we are using shiro.ini configuration.

2.1. Shiro.ini

First we have to set the  org.apache.shiro.authc.credential.PasswordMatcher passwordService field to BCryptPasswordService class. This one we create in the next step.

By the way, these are the only lines in shiro.ini to be changed for a switch to bcrypt.

[main]
	
#....

# password matcher
passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
#passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
passwordService = com.example.hunt.BCryptPasswordService
passwordMatcher.passwordService = $passwordService

ds = org.apache.shiro.jndi.JndiObjectFactory
ds.resourceName = java:comp/env/jdbc/TestDB

ds.requiredType = javax.sql.DataSource


jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.permissionsLookupEnabled = true
jdbcRealm.authenticationQuery = SELECT password FROM users WHERE email = ?
jdbcRealm.userRolesQuery = SELECT ur.role_name FROM users INNER JOIN users_roles ur ON users.id = ur.userid WHERE users.email = ?
jdbcRealm.permissionsQuery = SELECT permission FROM roles_permissions WHERE role_name = ?
jdbcRealm.credentialsMatcher = $passwordMatcher
jdbcRealm.dataSource=$ds
securityManager.realms = $jdbcRealm

[urls]
#...

Of course the passwordService can also be changed programmatically:

public JdbcRealm jdbcRealm() {
 *     final JdbcRealm jdbcRealm = new JdbcRealm();
 *     jdbcRealm.setDataSource(dataSource);
 *     final PasswordMatcher passwordMatcher = new PasswordMatcher();
 *     passwordMatcher.setPasswordService(new BCryptPasswordService());
 *     jdbcRealm.setCredentialsMatcher(passwordMatcher);
 *     return jdbcRealm;
 * }

2.2. BCryptPasswordService.java

Since BCryptPasswordService.java is not included with Apache Shiro at the time of the writing of this post, we have to implement it by ourselves.

For this we need the BCrypt password methods to be used in overriding methods like “passwordsMatch”. In turn we take Damien Miller’s bcrypt java implementation that comes with the package org.springframework.security.crypto.bcrypt which we also import.

package com.example.hunt;

import org.apache.shiro.authc.credential.PasswordService;
import org.springframework.security.crypto.bcrypt.BCrypt;
 
public class BCryptPasswordService implements PasswordService {

    @Override
    public String encryptPassword(Object plaintextPassword) throws IllegalArgumentException {
        final String str;
        if (plaintextPassword instanceof char[]) {
            str = new String((char[]) plaintextPassword);
        } else if (plaintextPassword instanceof String) {
            str = (String) plaintextPassword;
        } else {
            throw new IllegalArgumentException("Unsupported password type: " + plaintextPassword.getClass().getName());
        }
        return BCrypt.hashpw(str, BCrypt.gensalt());
    }

    @Override
    public boolean passwordsMatch(Object submittedPlaintext, String encrypted) {
        return BCrypt.checkpw(new String((char[]) submittedPlaintext), encrypted);
    }

    public boolean passwordsMatchString(String submittedPlaintext, String encrypted) {
        return BCrypt.checkpw( submittedPlaintext, encrypted);
    }
}

2.3. Usage in practice

Assume our user wants to change his old password and sends the old and the new one to our server via https.

We use the passwordsMatchString function of BCryptPasswordService to check the old password with the hash in our database.

//...
public class DoChangePassword {

    public void executeWithCurrentUser(Map<String, String[]> httpParameters) throws Exception {
        String oldPassword = ServletTools.formalCheckPassword(httpParameters, "oldPassword");
        String newPassword = ServletTools.formalCheckPassword(httpParameters, "newPassword");
        String newPasswordConfirm = ServletTools.formalCheckPassword(httpParameters, "newPasswordRepeat");

        if (!newPassword.equals(newPasswordConfirm)) {
            throw new BadRequestException("new passwords mismatch");
        }

        //check old password
        AccountLogic al = new AccountLogic(servletContextManager);
        User currentUser = servletContextManager.getCurrentUser();

        BCryptPasswordService bcps = new BCryptPasswordService();
        boolean oldPasswordIsCorrect = bcps.passwordsMatchString(oldPassword, currentUser.getPasswordHash()); 

        if (oldPasswordIsCorrect) {
            currentUser.setPasswordFromClearText(newPassword);
            al.updateUserData(currentUser);
        } else {
            throw new BadRequestException("Old Password Wrong");
        }

    }

}

In order to set the new password for the user we again use the BCryptPasswordService and the encryptPassword method seen before to create a hash.

///...
    public void setPasswordFromClearText(String password) {
        this.passwordClearText = password;
        this.passwordHashedString = new BCryptPasswordService().encryptPassword(password);
    }

4. Conclusion

This guide shows that it is really easy to change from SHA256 to bcrypt using Apache Shiro. Since SHA256 is the default hash function as has been criticized lately, for all who seek enhanced security this switch to brcypt is really worth it.

Leave a Reply

Your email address will not be published. Required fields are marked *