To main content

Apache Shiro: implementing new password hashing algorithms

Published by Benjamin Marwell on

As an Apache Shiro PMC member, I have occasionally contact to cryptographic functions. For example, Shiro 1.x allows hashed passwords in your shiro.ini configuration.

Now, everyone should know by now that just hashing (and salting) a password is not a good protection against brute force attacks. Even with hundreds or thousands of iterations, such a password can be prone to brute force attacks nowadays. This it is not a surprise that Lez Hazlewood (the original creator of Apache Shiro) had the idea to add an bcrypt implementation.

Now, when starting to implement bcrypt, I quickly found existing storage mechanisms as not sufficient. For example, hashes could be stored as hex or plain string, as well as a Shiro1 format which is very similar to the Posix format used in /etc/shadow.

New crypt formats

So I came up with the idea to create a new Shiro 2 Crypt Format and ditch the old ones for Shiro 2 (but keeping the classes for upgrading hints). There should really be no need for the insecure formats anymore. Also, as I find passwords potentially stored in your VCS of choice, it might an even better idea to ditch the old algorithms and include the new algorithms only if needed.

Shiro 1 crypt format

The Shiro1CryptFormat looks like this:

 $mcfFormatId$algorithmName$iterationCount$base64EncodedSalt$base64EncodedDigest

Shiro 2 crypt format

This is the format I adapted from /etc/shadow files. It looks like this:

class Argon2HashTest {

    private static final TEST_PASSWORD = "secret#shiro,password;Jo8opech";
    private static final TEST_PASSWORD_BS = new SimpleByteSource(TEST_PASSWORD)

@Test
    void testArgon2Hash() {
        // given
        def shiro2Format = '$shiro2$argon2id$v=19$m=4096,t=3,p=4$MTIzNDU2Nzg5MDEyMzQ1Ng$bjcHqfb0LPHyS13eVaNcBga9LF12I3k34H5ULt2gyoI'
        def expectedPassword = new SimpleByteSource(TEST_PASSWORD)

        // when
        def hash = new Shiro2CryptFormat().parse(shiro2Format) as Argon2Hash;
        def matchesPassword = hash.matchesPassword expectedPassword;

        // then
        assertEquals Argon2Parameters.ARGON2_VERSION_13, hash.argonVersion
        assertEquals 3, hash.iterations
        assertEquals 4096, hash.memoryKiB
        assertEquals 4, hash.parallelism
        assertTrue matchesPassword
    }
}

Pluggable Algorithm Providers

As Shiro does not use injection, I chose ServiceLoaders as a plug-in mechanism for loading algorithms. This way, Shiro can easily be extended. I also created an Argon2 implementation, while at it, contained in its own module.

You can see the Pull Request for SHIRO-290 on GitHub, waiting for more reviews. Be sure to add yours! 😊