Computer scienceProgramming languagesJavaAdditional instruments

Hashing java.security.MessageDigest

18 minutes read

The Java package java.security provides a class named MessageDigest. This class offers the functionality to generate secure messages using algorithms such as SHA-1 and MD5, and others. With this functionality, we can enable our applications to convert a message of arbitrary size into a fixed-length hash value. This value serves as a unique and reliable data identifier. In this topic, you will familiarize yourself with MessageDigest and its methods, integrating security into your applications.

When to use hashing

Hashing has many uses. Password authentication often employs hashing techniques as part of an overall security strategy. For example, consider a database system that stores user profile information in a table. Along with each username, the database saves a password hash value or digest, but not the actual user password. Passwords are plain-text and should not be stored in this manner. Upon login, the user provides their plain-text password, and the system calculates the hash value. The stored hash value is then compared with the newly calculated hash, and if they match, the password is valid.

The steps taken by the application after the user provides the password are as follows:

  • Create a byte array with the plain-text password and call it the message.
  • Create an instance with the preferred algorithm — MessageDigest.getInstance("SHA-1").
  • Provide the instance with the message — MessageDigest.update(message).
  • Digest the instance to get the final hash value — MessageDigest.digest().
  • Convert the digest to a hexadecimal base.
  • Compare the two hash values to determine the authentication.

An optional recommendation: when creating hash values for user passwords, you can add a salt to the plain-text password. This adds another layer of security to the process. The salt is a randomly selected text value, always of the same length and unique for each user. Concatenating the salt and password, and then digesting, results in a more secure value. The salt value will always need to be stored along with the digest. During user validation, the user provides the password as usual, and the application repeats the digesting process using the stored salt. Comparing the stored digest with the newly digested value will determine user authentication. Even if two users were to pick the same password, salting ensures a unique hash value for each.

A second example of when to use digesting is for checksum calculations. In this case, we want to safeguard the data integrity of files. The checksum is a unique digital signature or a fingerprint of the file and its contents. The steps required to perform a checksum on a file are similar to password authentication, with minor changes. The one consideration is that the input is now an entire file, unlike a short plain-text password.

The following steps need to be substituted:

  • Create a file input instance, providing it with the external file name.
  • Read the file and update the message instance until the end of the file.

Available algorithms

Before delving deeper into MessageDigest, let's list the available algorithms for use with the getInstance method. Use getAlgorithms to get the set of algorithms. This class is located in java.security.Security. All Java implementations will provide a minimum set of algorithms: MD5, SHA-1, and SHA-256. You'll have to provide a cryptographic service name as a string. In this case, we will use "MessageDigest"; other services are Signature, Cipher, and Mac. These are just groupings of algorithm types.

The following example uses getAlgorithms to list the available algorithms in your configuration.

import java.security.Security;
import java.util.Set;

public class AvailableAlgorithms {

    public static void main(String[] args) {
        Set<String> available = Security.getAlgorithms("MessageDigest");
        available.forEach(System.out::println);
    }
}

MessageDigest methods

Now that we have a list of available algorithms at our disposal, let's start using MessageDigest. To use MessageDigest, you apply the selected algorithm to an arbitrary string. Then, you apply the three main methods: getInstance, update, and digest. Let's create an initialized object using MessageDigest.getInstance. The returned MessageDigest object implements the specified digest algorithm — in this case, it's SHA-1.

Variable md points to the new instance object:

  • MessageDigest md = MessageDigest.getInstance("SHA-1")

Variable message contains the password in a byte array:

  • byte[] message = "MyPassword".getBytes()

The digest md is updated to include the message:

  • md.update(message)

The hash computation is completed by performing final operations, which return the hash value:

  • byte[] digest = md.digest()

The resulting digest can be converted to hex for viewing and comparing:

  • daa1f31819ed4928fd00e986e6bda6dab6b177dc

The update method receives the input for the instance of MessageDigest. Once updated, the digest method is called to complete the hash computation. After the final operations, such as padding, are completed, the MessageDigest object is reset to its initialized state.

Overloading digest

MessageDigest provides three overloads for the digest method. Below you can see the different signatures.

  • // overloaded digest method
    
    //These two: Complete the hash computation — requires prior update
        byte[] digest()
        int digest(byte[] buf, int offset, int len)
    
    //This one: Performs an update and then completes the hash computation
        byte[] digest(byte[] input)

The following two snippets serve the same purpose: a call to the update method with an input byte array, followed by a call to the digest method without any arguments.

  • void update(byte[] input)
    byte[] digest()

This is equivalent to a call to the digest method with an input byte array argument.

  • byte[] digest(byte[] input)

The following two forms return the computed hash value. The second form takes input and returns output. The advantage of this second form is that you can skip the update method for a one-time calculation. If there are multiple updates, the last one you handle here — all prior ones need to be processed by the update method.

  • byte[] digest() // Requires prior update
    
    byte[] digest(byte[] input) // Performs an update on its input

A third form of the digest method provides a buffer to store the computed digest. The returned int value is the number of bytes placed into the array. This form allows you to accumulate many hash values in one container. You can use this form in a loop by offsetting the location in the buf array.

  • int digest(byte[] buf, int offset, int len)
    
    // Returns:  int — the number of bytes placed into buf
    
    // Parameters:
    //   buf - output buffer for the computed digest
    //   offset - offset into the output buffer to begin storing the digest
    //   len - number of bytes within buf allotted for the digest

MessageDigest examples

As a first example, let's look at the basics of digesting, as shown in lines 9, 10, and 11. We create an instance md of MessageDigest, followed by an update, and then digest the message. Since the object gets updated in line 10, we use the simple form digest(). For the resulting output, we convert the digest to a hexadecimal number using the bytesToHex method and print it to the console.

1    import java.security.MessageDigest;
2    import java.security.NoSuchAlgorithmException;
3    
4    public class SecretHash {
5    
6        public static void main(String[] args) throws NoSuchAlgorithmException {
7            String secret = "MyPassword";
8            byte[] message = secret.getBytes();
9            MessageDigest md = MessageDigest.getInstance("SHA-1");
10           md.update(message);
11           byte[] digest = md.digest();
12           System.out.println(bytesToHex(digest));
13       }
14   
15       public static String bytesToHex(byte[] bytes) {
16           StringBuilder sb = new StringBuilder();
17           for (byte b : bytes) {
18               sb.append(String.format("%02x", b));
19           }
20           return sb.toString();
21       }
22   }
The output:
daa1f31819ed4928fd00e986e6bda6dab6b177dc

This second example processes multiple messages and stores them in an array, which is passed as input to digest. We use an offset of twenty due to the size of the SHA-1 algorithm output. The messages are then loaded into a list and finally sent to the process method. The process method handles the update and digesting.

1    import java.security.DigestException;
2    import java.security.MessageDigest;
3    import java.util.Arrays;
4    import java.util.List;
5    
6    public class MultiHash {
7    
8        public static void main(String[] args) throws Exception {
9    
10           MessageDigest md = MessageDigest.getInstance("SHA-1");
11           byte[] message1 = "MyPassword".getBytes();
12           byte[] message2 = "MyPassword".getBytes();
13           List<byte[]> list = Arrays.asList(message1, message2);
14           byte[] buff = new byte[40];
15           process(list, md, buff);
16       }
17   
18       public static void process(List<byte[]> list,
19                                  MessageDigest md,
20                                  byte[] buff)
21                                   throws DigestException {
22           int offset = 0;
23           int length = 20;
24           for ( byte[] l : list) {
25               md.update(l);
26               int count = md.digest(buff, offset, length);
27               System.out.println(bytesToHex(buff));
28               offset += 20;
29           }
30       }
31   
32       public static String bytesToHex(byte[] bytes) {
33           StringBuilder out = new StringBuilder();
34           for (byte b : bytes) {
35               out.append(String.format("%02x", b));
36           }
37           return out.toString();
38       }
39   }

The output shown below displays the first hash value. The second line, starting at byte twenty-one, shows the second value added.

daa1f31819ed4928fd00e986e6bda6dab6b177dc0000000000000000000000000000000000000000
daa1f31819ed4928fd00e986e6bda6dab6b177dcdaa1f31819ed4928fd00e986e6bda6dab6b177dc

Conclusion

The MessageDigest class makes it easy to add hashing to our applications. The class contains the methods for processing plain-text values into secure digest messages. After creating an instance, the update and digest methods will provide the desired hash values. Applications use hash functions to enhance security. Two common examples are password authentication and file checksum calculations. A hash value is generated from a plain-text message by a mathematical algorithm. You can't revert a hash value to the original value. For this reason, a higher level of security is achieved. Comparing stored hash values with newly generated ones allows for the detection of differences between the two, indicating an error. A hash value is also referred to as a digest.

7 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo