Java Hashing using MD5, SHA, PBKDF2, Bcrypt and Scrypt

Learn Java hashing algorithms in-depth for hashing passwords. A secure password hash is an encrypted sequence of characters obtained after applying specific algorithms and manipulations on user-provided passwords, which are generally very weak and easy to guess.

Many such hashing algorithms in Java can prove effective for password security.

Important

Please remember that once the password hash has been generated, we can not convert the hash back to the original password.

Each time a user login into the application, we must generate the password hash again and match it with the hash stored in the database.

So, if a user forgets his/her password, we will have to send him a temporary password; or ask him to reset the password. It’s common nowadays, right?

1. Simplest Password Hash with MD5 Algorithm

The MD5 Message-Digest Algorithm is a widely used cryptographic hash function that produces a 128-bit (16-byte) hash value. It’s very simple and straightforward; the basic idea is to map data sets of variable length to data sets of a fixed size.

To do this, the input message is split into chunks of 512-bit blocks. Padding is added to the end so that its length can be divided by 512.

These blocks are processed by the MD5 algorithm, which operates in a 128-bit state, and the result will be a 128-bit hash value. After applying MD5, the generated hash is typically a 32-digit hexadecimal number.

Here, the password to be encoded is often called the “message” and the generated hash value is called the message digest or simply “digest”.

1.1. Java MD5 Hashing Example

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class SimpleMD5Example 
{
  public static void main(String[] args) 
  {
    String passwordToHash = "password";
    String generatedPassword = null;

    try 
    {
      // Create MessageDigest instance for MD5
      MessageDigest md = MessageDigest.getInstance("MD5");

      // Add password bytes to digest
      md.update(passwordToHash.getBytes());

      // Get the hash's bytes
      byte[] bytes = md.digest();

      // This bytes[] has bytes in decimal format. Convert it to hexadecimal format
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < bytes.length; i++) {
        sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
      }

      // Get complete hashed password in hex format
      generatedPassword = sb.toString();
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }
    System.out.println(generatedPassword);
  }
}
5f4dcc3b5aa765d61d8327deb882cf99 

1.2. Disadvantages

  • Although MD5 is a widely spread hashing algorithm, is far from being secure, MD5 generates fairly weak hashes. Its main advantages are that it is fast, and easy to implement. But it also means that it is susceptible to brute-force and dictionary attacks.
  • Rainbow tables with words and hashes allow searching very quickly for a known hash and guessing the original password.
  • MD5 is not collision resistant which means that different passwords can eventually result in the same hash.

If you are using MD5 hash in your application, consider adding some salt to your security.

2. Making MD5 More Secure using Salt

Keep in mind, adding salt is not specific to MD5. We can add a Salt to every other algorithm also. So, please focus on how it is applied rather than its relation with MD5.

Wikipedia defines salt as random data that are used as an additional input to a one-way function that hashes a password or pass-phrase.

In more simple words, salt is some randomly generated text, which is appended to the password before obtaining hash.

The original intent of salting was primarily to defeat pre-computed rainbow table attacks that could otherwise be used to significantly improve the efficiency of cracking the hashed password database.

A more significant benefit is to slow down parallel operations that compare the hash of a password guess against many password hashes at once.

Important

We always need to use a SecureRandom to create good salts. The Java SecureRandom class supports the “SHA1PRNG” pseudo-random number generator algorithm, and we can take advantage of it.

2.1. How to generate Salt

Let’s see how we should generate salt.

private static String getSalt() 
    throws NoSuchAlgorithmException, NoSuchProviderException 
{
    // Always use a SecureRandom generator
    SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "SUN");

    // Create array for salt
    byte[] salt = new byte[16];

    // Get a random salt
    sr.nextBytes(salt);

    // return salt
    return salt.toString();
}

SHA1PRNG algorithm is used as a cryptographically strong pseudo-random number generator based on the SHA-1 message-digest algorithm.

Note that if a seed is not provided, it will generate a seed from a true random number generator (TRNG).

2.2. Generate MD5 with Salt

Now, let’s look at the modified MD5 hashing example:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

public class SaltedMD5Example 
{
    public static void main(String[] args)
            throws NoSuchAlgorithmException, NoSuchProviderException 
    {
        String passwordToHash = "password";
        String salt = getSalt();
        
        String securePassword = getSecurePassword(passwordToHash, salt);

        System.out.println(securePassword);

        String regeneratedPassowrdToVerify =
                getSecurePassword(passwordToHash, salt);

        System.out.println(regeneratedPassowrdToVerify);
    }

    private static String getSecurePassword(String passwordToHash,
            String salt) {
        String generatedPassword = null;
        try {
            // Create MessageDigest instance for MD5
            MessageDigest md = MessageDigest.getInstance("MD5");
            
            // Add password bytes to digest
            md.update(salt.getBytes());
            
            // Get the hash's bytes
            byte[] bytes = md.digest(passwordToHash.getBytes());
            
            // This bytes[] has bytes in decimal format;
            // Convert it to hexadecimal format
            StringBuilder sb = new StringBuilder();
            
            for (int i = 0; i < bytes.length; i++) {
                sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16)
                        .substring(1));
            }
            
            // Get complete hashed password in hex format
            generatedPassword = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return generatedPassword;
    }

    // Add salt
    private static String getSalt()
            throws NoSuchAlgorithmException, NoSuchProviderException 
    {
        // Always use a SecureRandom generator
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "SUN");

        // Create array for salt
        byte[] salt = new byte[16];

        // Get a random salt
        sr.nextBytes(salt);

        // return salt
        return salt.toString();
    }
}

Please note that you must now store this salt value for every password you hash. Because when user login back into the system, we must use only the originally generated salt to create the hash again to match with the stored hash. If a different salt is used (we are generating random salt), the then generated hash will be different.

Also, you might hear of the terms crazy hashing and salting. It generally refers to creating custom combinations.

alt + password + salt => Generated hash

Do not practice these crazy things. They do not help in making hashes further secure anyhow. If you want more security, choose a better algorithm.

3. Better Password Security using SHA Algorithms

The SHA (Secure Hash Algorithm) is a family of cryptographic hash functions. It is very similar to MD5, except it generates more strong hashes.

However, SHA hashes are not always unique, and it means that we could have equal hashes for two different inputs. When this happens, it’s called a “collision”. The chances of collision in SHA are less than MD5. But, do not worry about these collisions because they are very rare.

Java has four implementations of the SHA algorithm. They generate the following length hashes in comparison to MD5 (128-bit hash):

  • SHA-1 (Simplest one – 160 bits Hash)
  • SHA-256 (Stronger than SHA-1 – 256 bits Hash)
  • SHA-384 (Stronger than SHA-256 – 384 bits Hash)
  • SHA-512 (Stronger than SHA-384 – 512 bits Hash)

A longer hash is more challenging to break. That’s the core idea.

To get any implementation of the algorithm, pass it as a parameter to MessageDigest. e.g.

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

//OR

MessageDigest md = MessageDigest.getInstance("SHA-256");

3.1. Java SHA Hashing Example

Let’s create a test program to demonstrate SHA hash generation:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class SHAExample {

    public static void main(String[] args) throws NoSuchAlgorithmException {
        String passwordToHash = "password";
        String salt = getSalt();

        String securePassword = get_SHA_1_SecurePassword(passwordToHash, salt);
        System.out.println(securePassword);

        securePassword = get_SHA_256_SecurePassword(passwordToHash, salt);
        System.out.println(securePassword);

        securePassword = get_SHA_384_SecurePassword(passwordToHash, salt);
        System.out.println(securePassword);

        securePassword = get_SHA_512_SecurePassword(passwordToHash, salt);
        System.out.println(securePassword);
    }

    private static String get_SHA_1_SecurePassword(String passwordToHash,
            String salt) {
        String generatedPassword = null;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(salt.getBytes());
            byte[] bytes = md.digest(passwordToHash.getBytes());
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16)
                        .substring(1));
            }
            generatedPassword = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return generatedPassword;
    }

    private static String get_SHA_256_SecurePassword(String passwordToHash,
            String salt) {
        String generatedPassword = null;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(salt.getBytes());
            byte[] bytes = md.digest(passwordToHash.getBytes());
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16)
                        .substring(1));
            }
            generatedPassword = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return generatedPassword;
    }

    private static String get_SHA_384_SecurePassword(String passwordToHash,
            String salt) {
        String generatedPassword = null;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-384");
            md.update(salt.getBytes());
            byte[] bytes = md.digest(passwordToHash.getBytes());
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16)
                        .substring(1));
            }
            generatedPassword = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return generatedPassword;
    }

    private static String get_SHA_512_SecurePassword(String passwordToHash,
            String salt) {
        String generatedPassword = null;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            md.update(salt.getBytes());
            byte[] bytes = md.digest(passwordToHash.getBytes());
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16)
                        .substring(1));
            }
            generatedPassword = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return generatedPassword;
    }

    // Add salt
    private static String getSalt() throws NoSuchAlgorithmException {
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        byte[] salt = new byte[16];
        sr.nextBytes(salt);
        return salt.toString();
    }
}
e4c53afeaa7a08b1f27022abd443688c37981bc4

87adfd14a7a89b201bf6d99105b417287db6581d8aee989076bb7f86154e8f32

bc5914fe3896ae8a2c43a4513f2a0d716974cc305733847e3d49e1ea52d1ca50e2a9d0ac192acd43facfb422bb5ace88

529211542985b8f7af61994670d03d25d55cc9cd1cff8d57bb799c4b586891e112b197530c76744bcd7ef135b58d47d65a0bec221eb5d77793956cf2709dd012

Very quickly, we can say that SHA-512 generates the most robust Hash.

4. Stronger Hashes using PBKDF2WithHmacSHA1 Algorithm

So far, we have learned about creating secure hashes for passwords and using salt to make it even more secure. But the problem today is that hardwares have become so fast than any brute force attack using dictionary and rainbow tables, a bad actor can crack any password in less or more time.

To solve this problem, the general idea is to make brute force attacks slower to minimize damage. Our following algorithm works on this very concept.

The goal is to make the hash function slow enough to impede attacks but still fast enough not to cause a noticeable delay for the user.

This feature is essentially implemented using some CPU-intensive algorithms such as PBKDF2, Bcrypt or Scrypt. These algorithms take a work factor (also known as security factor) or iteration count as an argument.

Iteration count determines how slow the hash function will be. When computers become faster next year, we can increase the work factor to balance it out.

Java has implemented “PBKDF2” algorithm as “PBKDF2WithHmacSHA1“.

4.1. Java PBKDF2WithHmacSHA1 Hash Example

Let’s look at the example of how to use PBKDF2WithHmacSHA1 algorithm.

public static void main(String[] args) 
    throws NoSuchAlgorithmException, InvalidKeySpecException
{
    String  originalPassword = "password";

    String generatedSecuredPasswordHash 
        = generateStorngPasswordHash(originalPassword);
    System.out.println(generatedSecuredPasswordHash);
}
private static String generateStorngPasswordHash(String password) 
    throws NoSuchAlgorithmException, InvalidKeySpecException
{
    int iterations = 1000;
    char[] chars = password.toCharArray();
    byte[] salt = getSalt();

    PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, 64 * 8);
    SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

    byte[] hash = skf.generateSecret(spec).getEncoded();
    return iterations + ":" + toHex(salt) + ":" + toHex(hash);
}

private static byte[] getSalt() throws NoSuchAlgorithmException
{
    SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
    byte[] salt = new byte[16];
    sr.nextBytes(salt);
    return salt;
}

private static String toHex(byte[] array) throws NoSuchAlgorithmException
{
    BigInteger bi = new BigInteger(1, array);
    String hex = bi.toString(16);
    
    int paddingLength = (array.length * 2) - hex.length();
    if(paddingLength > 0)
    {
        return String.format("%0"  +paddingLength + "d", 0) + hex;
    }else{
        return hex;
    }
}
1000:5b4240333032306164:f38d165fce8ce42f59d366139ef5d9e1ca1247f0e06e503ee1a611dd9ec40876bb5edb8409f5abe5504aab6628e70cfb3d3a18e99d70357d295002c3d0a308a0

4.2. Verifying Passwords

The next step is to have a function that we can use to validate the password again when the user comes back and login.

public static void main(String[] args) 
    throws NoSuchAlgorithmException, InvalidKeySpecException
{
    String  originalPassword = "password";

    String generatedSecuredPasswordHash 
        = generateStorngPasswordHash(originalPassword);
    System.out.println(generatedSecuredPasswordHash);

    boolean matched = validatePassword("password", generatedSecuredPasswordHash);
    System.out.println(matched);

    matched = validatePassword("password1", generatedSecuredPasswordHash);
    System.out.println(matched);
}

private static boolean validatePassword(String originalPassword, String storedPassword) 
    throws NoSuchAlgorithmException, InvalidKeySpecException
{
    String[] parts = storedPassword.split(":");
    int iterations = Integer.parseInt(parts[0]);

    byte[] salt = fromHex(parts[1]);
    byte[] hash = fromHex(parts[2]);

    PBEKeySpec spec = new PBEKeySpec(originalPassword.toCharArray(), 
        salt, iterations, hash.length * 8);
    SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
    byte[] testHash = skf.generateSecret(spec).getEncoded();

    int diff = hash.length ^ testHash.length;
    for(int i = 0; i < hash.length && i < testHash.length; i++)
    {
        diff |= hash[i] ^ testHash[i];
    }
    return diff == 0;
}
private static byte[] fromHex(String hex) throws NoSuchAlgorithmException
{
    byte[] bytes = new byte[hex.length() / 2];
    for(int i = 0; i < bytes.length ;i++)
    {
        bytes[i] = (byte)Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
    }
    return bytes;
}

Please refer to functions from the above code samples. If found any difficulty, then download the source code attached at the end of the tutorial.

5. Hashes using Bcrypt and Scrypt

The concepts behind bcrypt is similar to the previous concept as in PBKDF2. It just happened to be that Java does not have any inbuilt support for bcrypt algorithm to make the attack slower but still, you can find one such implementation in the attached source code.

5.1. Creating Hash using Bcrypt with Salt

Let’s look at the sample usage code (BCrypt.java is available in the source code).

public class BcryptHashingExample
{
	public static void main(String[] args) throws NoSuchAlgorithmException
	{
		String  originalPassword = "password";
		String generatedSecuredPasswordHash = BCrypt.hashpw(originalPassword, BCrypt.gensalt(12));
		System.out.println(generatedSecuredPasswordHash);

		boolean matched = BCrypt.checkpw(originalPassword, generatedSecuredPasswordHash);
		System.out.println(matched);
	}
}
$2a$12$WXItscQ/FDbLKU4mO58jxu3Tx/mueaS8En3M6QOVZIZLaGdWrS.pK
true

5.2. Creating Hash using Scrypt with Salt

Like bcrypt, I have downloaded scrypt from github and added the source code of the scrypt algorithm in the sourcecode.

Let’s see how to use the implementation:

public class ScryptPasswordHashingDemo
{
	public static void main(String[] args) {
		String originalPassword = "password";
		String generatedSecuredPasswordHash = SCryptUtil.scrypt(originalPassword, 16, 16, 16);
		System.out.println(generatedSecuredPasswordHash);

		boolean matched = SCryptUtil.check("password", generatedSecuredPasswordHash);
		System.out.println(matched);

		matched = SCryptUtil.check("passwordno", generatedSecuredPasswordHash);
		System.out.println(matched);
	}
}
$s0$41010$Gxbn9LQ4I+fZ/kt0glnZgQ==$X+dRy9oLJz1JaNm1xscUl7EmUFHIILT1ktYB5DQ3fZs=
true
false

6. Conclusion

  1. Storing the text password with hashing is the most dangerous thing for application security today.
  2. MD5 provides basic hashing for generating secure password hash. Adding salt makes it further stronger.
  3. MD5 generates a 128-bit hash. Use the SHA algorithm to make it more secure, which produces hashes from 160-bit to 512-bit long. 512-bit is the strongest.
  4. Even SHA-hashed secure passwords can be cracked with today’s fast hardwares. To beat that, you will need algorithms to make the brute force attacks slower and minimize the impact. Such algorithms are PBKDF2, BCrypt and SCrypt.
  5. Please take a well-considered thought before applying the appropriate security algorithm.

To download the source code of the above algorithm examples, please follow the below link.

Happy Learning !!

Download sourcecode

Comments

Subscribe
Notify of
guest
88 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode