Hashing passwords in Spring applications

When handling account passwords, you need to make sure that passwords are transmitted through a secure medium and stored in a persistent and a non-reversibly hashed format.

A large majority of backend and web-based applications rely on authorization mechanisms which involve the user inserting a username and a password. When building such applications for the first time, an important question comes to mind. How to manage user passwords in a secure way?

In this post, we will discuss how to manage passwords and the different steps that you need to take when you are building Java or spring based applications.

Storing passwords in a database

The first thing that you need to decide when handling authentication data is where to save the user account passwords. Traditionally, you save this data in a database. This approach has worked for many years and will still work to this day.

Depending on the stack you are using, you could be saving your passwords in a relational database such as a Postgres DB or Oracle, or you could also store them on a NoSQL database such as MongoDB.

When storing passwords in a database, you should make sure that the connectivity between your application and the database is secure. Otherwise, all other security measures will not matter.

This means that you should be using mechanisms such as SSL to ensure the security of the transmitted data between your application and the database. Otherwise, anyone eavesdropping on your network traffic can figure out the contents of your data.

Hashing passwords

Now that you have decided to save your user accounts’ passwords in a database, you will also need to decide the format in which the passwords will be stored. You could store passwords in plain-text format. For example, a password such as “Password1234” will look like “Password1234” when you see it in the database.

However, I would like to stress that storing passwords in plain text format in your database can be a very bad idea, and for several reasons:

  • If your database is compromised for any reason, then the passwords of all the users in your database are also compromised. This means that the hacker can now impersonate one of your users in your system, without you knowing about it.
  • People often use the same password in more than one platform. For example, a user could use the same password on your system and on their Facebook account. So if your database is compromised, you could be risking not only accounts saved in your system, but also user accounts on other platforms.

Here is where hashing comes in. The idea is simple. Instead of storing the plain-text passwords, use a hashing function to hash the plain-text passwords, and store the hashed values in your database instead.

So how would you authenticate users? When a user is logging in, you do not need to match the input password with the stored value. Instead, you will hash the inserted password during login, and compare the output with the stored hashed value. If the values match, then the user has entered the correct password and is allowed to proceed.

This makes it very difficult to impersonate a user even when the hashed values are exposed as an attacker will still need the input value (the password) to the hashing function in order to produce a matching hash value.

Hashing is the process of applying a mathematical function (called a hashing function) to a piece of data in order to produce an output value. While we will not go too deep into detail about hashing, you will need to keep in mind a few things when choosing a hashing function:

  • Hashing functions should go in one way only. This means that if you decide to hash a value, then there is no mathematical way to “unhash” it to produce the original input.
  • Collision resistance. A collision happens when two or more different input values produce the same output when passed through the hashing function. Naturally you will want to avoid that if you want to build a secure system.
  • Hashing with salt. You will also need to choose a hashing algorithm that utilizes a “salt” value.
    • A salt value is a random value that is provided to the hashing function as an additional input. This allows you to store two input values with different hash outputs.
    • For example, if two users decide to use the password “Password”, they will be stored with the same hash value. This allows an attacker to perform a “Rainbow table” attack by comparing the hash value with pre-computed hashes in order to find the user’s input password.
    • If you hash the passwords with an additional salt value which is both unique and random, then even if the two users use the same password, the users will have different hashed values.
    • The randomly generated salt values will need to be saved in order to be used for authenticating the users in their login attempts.
  • Use slow hashing functions. This is important in order to make brute force attacks prohibitive. Ideally, the algorithm that you choose should be fast enough on CPUs but not fast or economical enough on GPUs, FPGAs or ASICs to make a brute force attack possible.

Another additional step that you could take to protect passwords is to apply multiple iterations of hashing to the user’s password. For example, you could use the output of the hashing function as an input to the same (or a different) hashing function. You could do this in as many iterations as you want.

This makes brute force attacks even more difficult, as an attacker will need to try many combinations, with each combination being hashed multiple times, making the brute force attack more expensive and not feasible in a reasonable amount of time.

Of course, the downside is that your application will need more resources if a lot of login attempts are being made in your system.

Implementing hashing in Java & Spring

Thankfully, there are a lot of hashing functionalities that come out of the box with Spring and Java. When hashing passwords, three popular algorithms come to mind. PBKDF2, scrypt and bcrypt.

We will discuss how to implement each of them in Java in order for you to be able to integrate them into your application.

PBKDF2

PBKDF2 stands for Password Based Key Derivative Function. The 2 in the name is there as it is the newer version of the algorithm. In the following implementation, you will not need to import any classpath dependencies. Please note that we used JDK 11 for this example, so your mileage may vary depending on your JDK version.

First, we generate the salt value using the SecureRandom Java class.

        SecureRandom secureRandom = new SecureRandom();
        byte[] salt = secureRandom.generateSeed(12);

The next step is to hash the password. We will request 10 iterations from the hashing algorithm and a 512 byte key size.

        PBEKeySpec pbeKeySpec = new PBEKeySpec("password".toCharArray(), salt, 10, 512);
        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        byte[] hash = skf.generateSecret(pbeKeySpec).getEncoded();

Now that you have the hash, you will need to store it somewhere. If you plan to store it in a database in String format, then you can use Base64 to encode the resulting hash byte array into a string.

        String base64Hash = Base64.getMimeEncoder().encodeToString(hash);

Please also make sure to store the salt value safely in order to be able to regenerate the hash during login attempts.

When a user attempts to authenticate, you can redo the steps described above and test that the stored and the generated hashes are equal. Below you will find the full example code.

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

public class PbkdF2Test {

    public static final Logger log = LoggerFactory.getLogger(PbkdF2Test.class);

    @Test
    public void test() throws NoSuchAlgorithmException, InvalidKeySpecException {

        
        SecureRandom secureRandom = new SecureRandom();
        
        //make sure to save this into a database
        byte[] salt = secureRandom.generateSeed(12);

        PBEKeySpec pbeKeySpec = new PBEKeySpec("password".toCharArray(), salt, 10, 512);
        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        byte[] hash = skf.generateSecret(pbeKeySpec).getEncoded();

        //converting to string to store into database
        String base64Hash = Base64.getMimeEncoder().encodeToString(hash);

        log.info(base64Hash);

        //Password matching steps
        String input = "password";

        //Here, you obtain the salt from the database
        PBEKeySpec pbeKeySpec2 = new PBEKeySpec(input.toCharArray(), salt, 10, 512);
        SecretKeyFactory skf2 = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        byte[] hash2 = skf.generateSecret(pbeKeySpec).getEncoded();

        String base64Hash2 = Base64.getMimeEncoder().encodeToString(hash);

        log.info(base64Hash2);

        log.info("check if hashes match, result: {}", base64Hash.equals(base64Hash2));
        
    }

}

If you attempt to run the code above, you will get something similar to this output.

00:01:37.153 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.PbkdF2Test - CdaihVpWON/2zY5MLccQX6Bl0ru10JaiO5KTOT3T9BEoEIQdz0iH8kuloVYG6onH34ox5q4VVBIE
AikO7ZHRSw==
00:01:37.158 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.PbkdF2Test - CdaihVpWON/2zY5MLccQX6Bl0ru10JaiO5KTOT3T9BEoEIQdz0iH8kuloVYG6onH34ox5q4VVBIE
AikO7ZHRSw==
00:01:37.159 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.PbkdF2Test - check if hashes match, result: true


Process finished with exit code 0

bcrypt

The following example utilizes the power of Spring-Security in order to generate the bcrypt hashes. bcrypt is a password hashing function based on the Blowfish cipher. It has been published in 1999 and has since been a favorite choice among software developers for hashing passwords.

This is because bcrypt can be iteratively applied to passwords in order to offset advances in hardware processing speeds, making it harder to brute-force.

Before you start, make sure you have Spring-security library in your classpath. If you are using Spring-boot, then the dependency will look as follows.

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>

If you are not using spring boot, then you can add the required dependency as follows.

    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-core</artifactId>
      <version>X.X.X</version>
    </dependency>

Using bcrypt with Spring is quite simple. All you need to do is to start an instance of the BCryptPasswordEncoder. There are two main methods that you will need from the encoder. The encode method, which generates the hash value, and the matches method which compares a password and a bcrypt hash to figure out if the password matches the hashed value. Please find below an example usage.

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class BCryptTest {

    public static final Logger log = LoggerFactory.getLogger(BCryptTest.class);

    @Test
    public void testHashWithSalt(){

        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();

        String hashedPass1 = bCryptPasswordEncoder.encode("password");
        String hashedPass2 = bCryptPasswordEncoder.encode("password");

        log.info(hashedPass1);
        log.info(hashedPass2);

        log.info("Result of matching: {}" ,bCryptPasswordEncoder.matches("password", hashedPass1));
        log.info("Result of matching: {}" ,bCryptPasswordEncoder.matches("password", hashedPass2));

        //trying to match with a previously generated value for same password. Should be 'true'
        log.info("Result of matching: {}" ,bCryptPasswordEncoder.matches("password", "$2a$10$mP.DNH3LIy/PeIM84y1nhuq76w98b8ANcxH3bzxPjiXHUdSl3XFri"));
        log.info("Result of matching: {}" ,bCryptPasswordEncoder.matches("password", "$2a$10$mP.DNH3LIy/PeIM84y1nhuq77w98b8ANcxH3bzxPjiXHUdSl3XFri"));
        log.info("Result of matching: {}" ,bCryptPasswordEncoder.matches("paSSword", "$2a$10$mP.DNH3LIy/PeIM84y1nhuq76w98b8ANcxH3bzxPjiXHUdSl3XFri"));

    }

}

If you run the program above, you will have an output similar to this.

00:27:50.520 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.BCryptTest - $2a$10$0LeSzg9W/wdMCaVxCjd.CuHu9YEncUTaTI/lHXbVgyBwhVSjbc4RC
00:27:50.524 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.BCryptTest - $2a$10$MzKLAXdP3F9ci.wtzuLsyelAb39TmPHDCEYkCIx9QfFR9z6GrgjlC
00:27:50.641 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.BCryptTest - Result of matching: true
00:27:50.754 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.BCryptTest - Result of matching: true
00:27:50.864 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.BCryptTest - Result of matching: true
00:27:50.972 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.BCryptTest - Result of matching: false
00:27:51.081 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.BCryptTest - Result of matching: false


Process finished with exit code 0

Please keep in mind that the salt value is generated automatically by the encoder and is included inside the hash. Also note that you can configure different parameters of the encoder, such as the log rounds to be used, the version of bcrypt and a SecurityRandom for extra security.

scrypt

scrypt is another hashing function, which is password based. It was first published in 2009. Due to memory constraints, large scale hardware brute force attacks against scrypt can be extremely expensive.

In this example, we used Spring-Security as well. However, you will also need to add a dependency to “Bouncy Castle Core”.

		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk15on</artifactId>
			<version>1.64</version>
		</dependency>

The Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms and it is used by Spring for their implementation of the SCryptPasswordEncoder.

The encoder can be used similar to the bcrypt encoder. Take a look at the code below.

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;

public class ScryptTest {

    public static final Logger log = LoggerFactory.getLogger(ScryptTest.class);

    @Test
    public void test(){
        SCryptPasswordEncoder sCryptPasswordEncoder = new SCryptPasswordEncoder();
        sCryptPasswordEncoder.encode("password");

        String hashedPass1 = sCryptPasswordEncoder.encode("password");
        String hashedPass2 = sCryptPasswordEncoder.encode("password");

        log.info(hashedPass1);
        log.info(hashedPass2);

        log.info("Result of matching: {}" ,sCryptPasswordEncoder.matches("password", hashedPass1));
        log.info("Result of matching: {}" ,sCryptPasswordEncoder.matches("password", hashedPass2));

    }



}

If you run the code above, you will have the following result.

00:40:30.502 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.ScryptTest - $e0801$kp8Si63et+gMiSqvqRKOQhN6jHzKxM25knb7WGusuch1no0rhezghKXv63Yd2O0VbeJ39PFOSDpIkZalL+D6LQ==$i5m9JjUhfh72d06zKhdW0/80QHQbnsyY0pOJfAMI9gc=
00:40:30.505 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.ScryptTest - $e0801$WhhFwotPHmtCuItQqNdd2E4GKliannYtjpVtuWgPSEyJGTGU58/8gS1SVnNbnwPA+TUxRNlJhpDiJEekm9JMgw==$TqeCAJUkgVlpimOhj6Fz/CjcKVTDJytxjKkHVvDnYoo=
00:40:30.602 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.ScryptTest - Result of matching: true
00:40:30.686 [main] INFO com.nullbeans.customerservice.incidentmanagement.utils.crypto.ScryptTest - Result of matching: true


Process finished with exit code 0

Notice that the salt values are included in the hashed values. Notice also that the password matched the two hashes, even though they are of different values.

Please also note that the scrypt encoder can be configured with additional parameters, such as the cpu difficulty (cpu cost), memory cost, salt length, among others.

Summary and further reading

If you find securing passwords and storing them safely in databases manually is a demanding task, then you can also use a secrets management engine such as Hashicorp Vault.

The nice thing about Vault is that it is open source, and it can be used not only to store user passwords, but other important data such as database passwords, passwords to external systems, authentication tokens, etc.. But that is a story for another day 🙂

In this post, we discussed why is it important to hash passwords, what are we looking for in hashing functions, and how to perform basic implementations of hashing in Java and Spring.

References

Hashing passwords in Spring applications
Scroll to top