Hashing Passwords in Java With BCrypt

BCrypt is a one-way salted hash function based on the Blowfish cipher. It provides several enhancements over plain text passwords (unfortunately this still happens quite often) and traditional hashing algorithms (md5). It wouldn't be accurate to say BCrypt is the best way to store passwords but it should be good enough. Algorithms such as PBKDF2 could be used as a more thoroughly tested algorithm but BCrypt is commonly used as well. jBCrypt is a Java implementation of BCrypt.

BCrypt Features

jBCrypt Example Wrapper

We will be wrapping the standard jBCrypt with our own methods to allow auto updating the iterations on the fly.

public class UpdatableBCrypt {
    private static final Logger log = LoggerFactory.getLogger(UpdatableBCrypt.class);

    private final int logRounds;

    public UpdatableBCrypt(int logRounds) {
        this.logRounds = logRounds;
    }

    public String hash(String password) {
        return BCrypt.hashpw(password, BCrypt.gensalt(logRounds));
    }

    public boolean verifyHash(String password, String hash) {
        return BCrypt.checkpw(password, hash);
    }

    public boolean verifyAndUpdateHash(String password, String hash, Function<String, Boolean> updateFunc) {
        if (BCrypt.checkpw(password, hash)) {
            int rounds = getRounds(hash);
            // It might be smart to only allow increasing the rounds.
            // If someone makes a mistake the ability to undo it would be nice though.
            if (rounds != logRounds) {
                log.debug("Updating password from {} rounds to {}", rounds, logRounds);
                String newHash = hash(password);
                return updateFunc.apply(newHash);
            }
            return true;
        }
        return false;
    }

    /*
     * Copy pasted from BCrypt internals :(. Ideally a method
     * to exports parts would be public. We only care about rounds
     * currently.
     */
    private int getRounds(String salt) {
        char minor = (char)0;
        int off = 0;

        if (salt.charAt(0) != '$' || salt.charAt(1) != '2')
            throw new IllegalArgumentException ("Invalid salt version");
        if (salt.charAt(2) == '$')
            off = 3;
        else {
            minor = salt.charAt(2);
            if (minor != 'a' || salt.charAt(3) != '$')
                throw new IllegalArgumentException ("Invalid salt revision");
            off = 4;
        }

        // Extract number of rounds
        if (salt.charAt(off + 2) > '$')
            throw new IllegalArgumentException ("Missing salt rounds");
        return Integer.parseInt(salt.substring(off, off + 2));
    }
}

Static Hashing Utility Class

Let's make a static utility class to handle everything for us. Ideally, we will periodically update the number of iterations in this class based on the recommended intervals.

// This should be updated every year or two.
private static final UpdatableBCrypt bcrypt = new UpdatableBCrypt(11);

public static String hash(String password) {
    return bcrypt.hash(password);
}

public static boolean verifyAndUpdateHash(String password, String hash, Function<String, Boolean> updateFunc) {
    return bcrypt.verifyAndUpdateHash(password, hash, updateFunc);
}

BCrypt Java Example With Updatable Iterations

Putting it all together.

// Mini function to test updates.
String[] mutableHash = new String[1];
Function<String, Boolean> update = hash -> { mutableHash[0] = hash; return true; };

String hashPw1 = Hashing.hash("password");
log.debug("hash of pw1: {}", hashPw1);
log.debug("verifying pw1: {}", Hashing.verifyAndUpdateHash("password", hashPw1, update));
log.debug("verifying pw1 fails: {}", Hashing.verifyAndUpdateHash("password1", hashPw1, update));
String hashPw2 = Hashing.hash("password");
log.debug("hash of pw2: {}", hashPw2);
log.debug("verifying pw2: {}", Hashing.verifyAndUpdateHash("password", hashPw2, update));
log.debug("verifying pw2 fails: {}", Hashing.verifyAndUpdateHash("password2", hashPw2, update));

UpdatableBCrypt oldHasher = new UpdatableBCrypt(7);
String oldHash = oldHasher.hash("password");
log.debug("hash of oldHash: {}", oldHash);
log.debug("verifying oldHash: {}, hash upgraded to: {}",
          Hashing.verifyAndUpdateHash("password", oldHash, update),
          mutableHash[0]);

Notice how the first and second password are the same but the hashes are different. We are also able to upgrade an older version of a BCrypt password using a low number of iterations to a higher number on the fly. In the real world, the passed in Function would be updating the database.

2017-08-02 01:07:38.757 [main] DEBUG com.stubbornjava.common.Hashing - hash of pw1: $2a$11$MXOOO1JYngri2arcL6Cic.KuBujhqgz.B2ri6szqN2/cfsdiQa7se
2017-08-02 01:07:38.936 [main] DEBUG com.stubbornjava.common.Hashing - verifying pw1: true
2017-08-02 01:07:39.108 [main] DEBUG com.stubbornjava.common.Hashing - verifying pw1 fails: false
2017-08-02 01:07:39.284 [main] DEBUG com.stubbornjava.common.Hashing - hash of pw2: $2a$11$TckEYxY/0DPf6OfpQhXKP./hl45UlgXYs0jQFsZZCwBEjUCo7bUKy
2017-08-02 01:07:39.466 [main] DEBUG com.stubbornjava.common.Hashing - verifying pw2: true
2017-08-02 01:07:39.647 [main] DEBUG com.stubbornjava.common.Hashing - verifying pw2 fails: false
2017-08-02 01:07:39.660 [main] DEBUG com.stubbornjava.common.Hashing - hash of oldHash: $2a$07$LMVTcMYGZZ6ORi4oPUGPAe3v8Kqpl.UCzKO2s8yfFN6c9vfM4szKW
2017-08-02 01:07:39.671 [main] DEBUG c.s.common.UpdatableBCrypt - Updating password from 7 rounds to 11
2017-08-02 01:07:39.848 [main] DEBUG com.stubbornjava.common.Hashing - verifying oldHash: true, hash upgraded to: $2a$11$1MvimUN3YO4G9DGY8G7rqOyPIjyye7jMYdIu8/BXL7t.e9CECj5Oa

 

 

 

 

Top