Hashing a Password in Java

1. Overview

In this tutorial, we’ll be discussing the importance of password hashing.

We’ll take a quick look at what it is, why it’s important, and some secure and insecure ways of doing it in Java.

2. What’s Hashing?

Hashing is the process of generating a string, or hash, from a given message using a mathematical function known as a cryptographic hash function.

While there are several hash functions out there, those tailored to hashing passwords need to have four main properties to be secure:

  1. It should be deterministic: the same message processed by the same hash function should always produce the same hash
  2. It’s not reversible: it’s impractical to generate a message from its hash
  3. It has high entropy: a small change to a message should produce a vastly different hash
  4. And it resists collisions: two different messages should not produce the same hash

A hash function that has all four properties is a strong candidate for password hashing since together they dramatically increase the difficulty in reverse-engineering the password from the hash.

Also, though, password hashing functions should be slow. A fast algorithm would aid brute force attacks in which a hacker will attempt to guess a password by hashing and comparing billions (or trillions) of potential passwords per second.

Some great hash functions that meet all these criteria arePBKDF2, BCrypt, and SCrypt. But first, let’s take a look at some older algorithms and why they are no longer recommended

3. Not Recommended: MD5

Our first hash function is the MD5 message-digest algorithm, developed way back in 1992.

Java’s MessageDigest makes this easy to calculate and can still be useful in other circumstances.

However, over the last several years, MD5 was discovered to fail the fourth password hashing property in that it became computationally easy to generate collisions. To top it off, MD5 is a fast algorithm and therefore useless against brute-force attacks.

Because of these, MD5 is not recommended.

4. Not Recommended: SHA-512

Next, we’ll look at SHA-512, which is part of the Secure Hash Algorithm family, a family that began with SHA-0 back in 1993.

4.1. Why SHA-512?

As computers increase in power, and as we find new vulnerabilities, then researchers derive new versions of SHA. Newer versions have a progressively longer length, or sometimes researchers publish a new version of the underlying algorithm.

SHA-512 represents the longest key in the third generation of the algorithm.

While there are now more secure versions of SHA, SHA-512 is the strongest that is implemented in Java.

4.2. Implementing in Java

Now, let’s have a look at implementing the SHA-512 hashing algorithm in Java.

First, we have to understand the concept of salt. Simply put, this is a random sequence that is generated for each new hash.

By introducing this randomness, we increase the hash’s entropy, and we protect our database against pre-compiled lists of hashes known as rainbow tables.

Our new hash function then becomes roughly:

salt <- generate-salt;
hash <- salt + ':' + sha512(salt + password)

4.3. Generating a Salt

To introduce salt, we’ll use the SecureRandom class from java.security:

SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);

Then, we’ll use the MessageDigest class to configure the SHA-512 hash function with our salt:

MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(salt);

And with that added, we can now use the digest method to generate our hashed password:

byte[] hashedPassword = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));

4.4. Why Is It Not Recommended?

When employed with salt, SHA-512 is still a fair optionbut there are stronger and slower options out there.

Also, the remaining options we’ll cover have an important feature: configurable strength.

5. PBKDF2, BCrypt, and SCrypt

PBKDF2, BCrypt, and SCrypt are three recommended algorithms.

5.1. Why Are Those Recommended?

Each of these is slow, and each has the brilliant feature of having a configurable strength.

This means that as computers increase in strength, we can slow down the algorithm by changing the inputs.

5.2. Implementing PBKDF2 in Java

Now, salts are a fundamental principle of password hashing, and so we need one for PBKDF2, too:

SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);

Next, we’ll create a PBEKeySpec and a SecretKeyFactory which we’ll instantiate using the PBKDF2WithHmacSHA1 algorithm:

KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

The third parameter (65536) is effectively the strength parameter. It indicates how many iterations that this algorithm run for, increasing the time it takes to produce the hash.

Finally, we can use our SecretKeyFactory to generate the hash:

byte[] hash = factory.generateSecret(spec).getEncoded();

5.3. Implementing BCrypt and SCrypt in Java

So, it turns out that BCrypt and SCrypt support don’t yet ship with Java, though some Java libraries support them.

One of those libraries is Spring Security.

6. Password Hashing With Spring Security

Although Java natively supports both the PBKDF2 and SHA hashing algorithms, it doesn’t support BCrypt and SCrypt algorithms.

Luckily for us, Spring Security ships with support for all these recommended algorithms via the PasswordEncoder interface:

  • MessageDigestPasswordEncoder gives us MD5 and SHA-512
  • Pbkdf2PasswordEncoder gives us PBKDF2
  • BCryptPasswordEncoder gives us BCrypt, and
  • SCryptPasswordEncoder gives us SCrypt

The password encoders for PBKDF2, BCrypt, and SCrypt all come with support for configuring the desired strength of the password hash.

We can use these encoders directly, even without having a Spring Security-based application. Or, if we are protecting our site with Spring Security, then we can configure our desired password encoder through its DSL or via dependency injection.

And, unlike our examples above, these encryption algorithms will generate the salt for us internally. The algorithm stores the salt within the output hash for later use in validating a password.

7. Conclusion

So, we’ve taken a deep dive into password hashing; exploring the concept and its uses.

And we’ve taken a look at some historical hash functions as well as some currently implemented ones before coding them in Java.

Finally, we saw that Spring Security ships with its password encrypting classes, implementing an array of different hash functions.

As always, the code is available on GitHub.

Related posts:

Java Program to Generate a Random UnDirected Graph for a Given Number of Edges
Spring Boot - Runners
Hướng dẫn Java Design Pattern – Service Locator
Form Validation with AngularJS and Spring MVC
Converting a Stack Trace to a String in Java
Loại bỏ các phần tử trùng trong một ArrayList như thế nào?
Convert a Map to an Array, List or Set in Java
Java Program to Implement Strassen Algorithm
Read an Outlook MSG file
Pagination and Sorting using Spring Data JPA
Spring @Primary Annotation
Java Program to Perform Searching Based on Locality of Reference
Introduction to Netflix Archaius with Spring Cloud
Spring Cloud AWS – S3
Case-Insensitive String Matching in Java
Java Program to Check the Connectivity of Graph Using DFS
Spring WebClient vs. RestTemplate
Introduction to Using FreeMarker in Spring MVC
JPA/Hibernate Persistence Context
Find the Registered Spring Security Filters
Quick Guide to Spring MVC with Velocity
Java Program to Implement Hash Tables chaining with Singly Linked Lists
Java Program to Check if a Directed Graph is a Tree or Not Using DFS
Concurrent Test Execution in Spring 5
Một số tính năng mới về xử lý ngoại lệ trong Java 7
The Dining Philosophers Problem in Java
Java Program to Encode a Message Using Playfair Cipher
Simple Single Sign-On with Spring Security OAuth2
Java Program to Implement Uniform-Cost Search
Spring’s RequestBody and ResponseBody Annotations
Xử lý ngoại lệ trong Java (Exception Handling)
Câu lệnh điều khiển vòng lặp trong Java (break, continue)