Using HMac Sha256 for Message Authentication (MAC) in Java

Secure message authentication using a MAC generated from a secret key with a password.

“There is some good in this world, and it’s worth fighting for.” ― J.R.R. Tolkien, The Two Towers

1. Introduction

A Message Authentication Code or a MAC provides a way to guarantee that a message (a byte array) has not been modified in transit. It is similar to a message digest to calculate a hash, but uses a secret key so that only a person with the secret key can verify the authenticity of the message.

Using a MAC to ensure safe transmission of messages requires that the two parties share a secret key to be able to generate and verify the MAC. There are two approaches available here – the two parties can share a secret key directly. Or the secret key can be generated using a password. We investigate both approaches below.

2. Java Imports

The following java imports are required.

import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.security.SecureRandom;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.Mac;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

3. Generating a Key

Let us now look into generating a secret key that the parties exchanging messages can share. Either party can generate the key as shown here and send it to the other party via a secure channel.

String algo = ...;
KeyGenerator kgen = KeyGenerator.getInstance(algo);
SecretKey skey = kgen.generateKey();

The following KeyGenerator algorithms are supported in my version of java. Of these, ones starting with HMAC are useful as a secret key for a MAC.

AES, ARCFOUR, BLOWFISH, DES, DESEDE, HMACMD5, HMACSHA1, HMACSHA224,
HMACSHA256, HMACSHA384, HMACSHA512, RC2

4. Saving and Restoring a Key

You will need to send the key to the receiver to enable verification. So you need to be able to save and restore the key. Here is how you can save it to a file.

try (FileOutputStream out = new FileOutputStream(outputFile)) {
    out.write(key.getEncoded());
}

And restoring the key is also quite simple.

byte[] keyb = Files.readAllBytes(Paths.get(outputFile));
SecretKey skey = new SecretKeySpec(keyb, type);

5. Generating the Message Authentication Code (MAC)

Once you generate the key, you can proceed with generating the MAC for a message.

String algo = ...;
Mac mac = Mac.getInstance(algo);
mac.init(skey);
try (FileInputStream in = new FileInputStream(inputFile)) {
    byte[] macb = processFile(mac, in);
    System.out.println(inputFile + ": " + encoder.encodeToString(macb));
}

Use one of the following values for the algorithm (algo):

HMACMD5, HMACSHA1, HMACSHA224, HMACSHA256, HMACSHA384, HMACSHA512

The static method processFile() is defined as follows:

static private final byte[] processFile(Mac mac,InputStream in)
    throws java.io.IOException
{
    byte[] ibuf = new byte[1024];
    int len;
    while ((len = in.read(ibuf)) != -1) {
        mac.update(ibuf, 0, len);
    }
    return mac.doFinal();
}

Once the MAC is obtained, you can send it to the other party along with the file for verification.

6. Using a Password for Key Generation

While one way of generating a secret key is to directly use a KeyGenerator as shown above, sometimes it is more convenient to use a password to generate the key. This can be accomplished using a SecretKeyFactory.

One aspect of using a password for key generation is that it requires the use of a salt. This salt can be generated using the SecureRandom class while generating the key, but the same salt must be used again for verification. This means the salt also must be communicated to the message receiver, but it need not be kept secret – it can be transmitted as plain-text.

The following code generates and prints the salt for transmission.

Base64.Encoder encoder = Base64.getEncoder();
SecureRandom srandom = new SecureRandom();
...
byte[] salt = new byte[8];
srandom.nextBytes(salt);
System.out.println("Salt: " + encoder.encodeToString(salt));

And now for the generation of the secret key.

char[] password = ...;
SecretKeyFactory factory = SecretKeyFactory.getInstance(algo);
KeySpec spec = new PBEKeySpec(password, salt, 10000, 128);
SecretKey key = factory.generateSecret(spec);

Use one of the following values for the algorithm (algo):

PBKDF2WITHHMACSHA1, PBKDF2WITHHMACSHA224, PBKDF2WITHHMACSHA256, PBKDF2WITHHMACSHA384, PBKDF2WITHHMACSHA512

Once you have the SecretKey, the procedure for computing the MAC is the same as above. However, you will need to use one of the following algorithms:

PBEWITHHMACSHA1, PBEWITHHMACSHA224, PBEWITHHMACSHA256, PBEWITHHMACSHA384, PBEWITHHMACSHA512

Conclusion

In this article, we learned how to compute a MAC (Message Authentication Code) for a message. A MAC requires a secret key which needs to be shared among the parties in the exchange. A password can be used for generating the secret key too.