HMAC Verification

Hashed message authentication codes (HMAC) are a type of message authentication code employing a cryptographic hash function and secret key. It provides message integrity (via the hash function and encryption) and authentication (via encryption). It does not provide non-repudiation, as it uses a symmetric key which anyone can use to sign a message (no guarantee it came from a specific entity). If non-repudiation is required, use Digital Signatures.

The following is a simple example HMAC class using JDK8 HMAC algorithms.

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
 
/**
 * HMAC class.
 * @author Ray Hylock
 */
public class HMAC {
    // defaults
    private static final Algorithms DEFAULT_ALGO = Algorithms.HmacSHA512;
    private static final String CHARSET = "UTF8";
     
    /** 
     * Algorithms: 
     * {@code HmacMD5}, {@code HmacSHA1}, {@code HmacSHA224}, {@code HmacSHA256}, 
     * {@code HmacSHA384}, {@code HmacSHA512} (default)*/
    public static enum Algorithms { 
        HmacMD5, HmacSHA1, HmacSHA224, HmacSHA256, HmacSHA384, HmacSHA512
    }
     
    /** Do not instantiate. */
    private HMAC(){}
     
    /**************************************************************************
     *                                SIGNERS                                 *
     **************************************************************************/
    /**
     * Signs the message using the {@code secretKey} and returns the HMAC
     * as a {@code byte[]}.
     * @param secretKey secret key
     * @param message   message to sign
     * @return          signature as a {@code byte[]}
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     * @throws InvalidKeyException {@link InvalidKeyException}
     * @throws UnsupportedEncodingException {@link UnsupportedEncodingException}
     */
    public static byte[] sign(String secretKey, String message) 
            throws NoSuchAlgorithmException, InvalidKeyException, 
            UnsupportedEncodingException  {
        return sign(DEFAULT_ALGO, secretKey.toCharArray(), message);
    }
     
    /**
     * Signs the message using the {@code secretKey} and returns the HMAC
     * as a {@code byte[]}.
     * @param secretKey secret key
     * @param message   message to sign
     * @return          signature as a {@code byte[]}
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     * @throws InvalidKeyException {@link InvalidKeyException}
     * @throws UnsupportedEncodingException {@link UnsupportedEncodingException}
     */
    public static byte[] sign(char[] secretKey, String message) 
            throws NoSuchAlgorithmException, InvalidKeyException, 
            UnsupportedEncodingException  {
        return sign(DEFAULT_ALGO, secretKey, message);
    }
     
    /**
     * Signs the message using the {@code secretKey} and returns the HMAC
     * as a {@code byte[]}.
     * @param algorithm {@link Algorithms}
     * @param secretKey secret key
     * @param message   message to sign
     * @return          signature as a {@code byte[]}
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     * @throws InvalidKeyException {@link InvalidKeyException}
     * @throws UnsupportedEncodingException {@link UnsupportedEncodingException}
     */
    public static byte[] sign(Algorithms algorithm, String secretKey, 
            String message) throws NoSuchAlgorithmException, InvalidKeyException, 
            UnsupportedEncodingException  {
        return sign(algorithm, secretKey.toCharArray(), message);
    }
     
    /**
     * Signs the message using the {@code secretKey} and returns the HMAC
     * as a {@code byte[]}.
     * @param algorithm {@link Algorithms}
     * @param secretKey secret key
     * @param message   message to sign
     * @return          signature as a {@code byte[]}
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     * @throws InvalidKeyException {@link InvalidKeyException}
     * @throws UnsupportedEncodingException {@link UnsupportedEncodingException}
     */
    public static byte[] sign(Algorithms algorithm, char[] secretKey, 
            String message) throws NoSuchAlgorithmException, InvalidKeyException, 
            UnsupportedEncodingException  {
        // get instance
        Mac mac = Mac.getInstance(algorithm.name());
 
        // convert to bytes
        byte sk[] = new byte[secretKey.length << 1]; 
        for(int i=0; i<secretKey.length; i++){
            sk[i << 1] = (byte)(secretKey[i] >> 8);
            sk[(i << 1)+1] = (byte) secretKey[i];
        }
 
        // init and return
        mac.init(new SecretKeySpec(sk, algorithm.name()));
        return mac.doFinal(message.getBytes(CHARSET));
    }
     
    /**************************************************************************
     *                               VERIFIERS                                *
     **************************************************************************/
    /**
     * Verifies the message using the {@code publicKey}.
     * @param algorithm {@link Algorithms}
     * @param secretKey secret key
     * @param message   message to verify
     * @param hmac      HMAC to compare to
     * @return          {@code true} if valid, {@code false} otherwise
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     * @throws InvalidKeyException {@link InvalidKeyException}
     * @throws UnsupportedEncodingException {@link UnsupportedEncodingException}
     */
    public static boolean verify(Algorithms algorithm, String secretKey, 
            String message, byte[] hmac) throws NoSuchAlgorithmException, 
            InvalidKeyException, UnsupportedEncodingException {
        return verify(algorithm, secretKey.toCharArray(), message, hmac);
    }
     
    /**
     * Verifies the message using the {@code publicKey}.
     * @param algorithm {@link Algorithms}
     * @param secretKey secret key
     * @param message   message to verify
     * @param hmac      HMAC to compare to
     * @return          {@code true} if valid, {@code false} otherwise
     * @throws NoSuchAlgorithmException {@link NoSuchAlgorithmException}
     * @throws InvalidKeyException {@link InvalidKeyException}
     * @throws UnsupportedEncodingException {@link UnsupportedEncodingException}
     */
    public static boolean verify(Algorithms algorithm, char[] secretKey, 
            String message, byte[] hmac) throws NoSuchAlgorithmException, 
            InvalidKeyException, UnsupportedEncodingException {
        byte[] c = sign(algorithm, secretKey, message);
        return java.util.Arrays.equals(hmac, c);
    }
}

The following is an example implementation of the HMAC class that (1) signs a message with a secret key and verifies it and (2) modifies the message like an attacker would (without resigning), which fails to verify.

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class HMACTest {
    public static void main(String[] args) throws NoSuchAlgorithmException, 
            InvalidKeyException, UnsupportedEncodingException{
        // message and secret key
        String message = "This is a test message";
        String secretKey = "kasd8fj2wd98j098827(*&(*Y$#en";
         
        // HMAC SHA512 - verify == true
        byte[] hmac = HMAC.sign(HMAC.Algorithms.HmacSHA512, secretKey, message);
        boolean verify = HMAC.verify(HMAC.Algorithms.HmacSHA512, secretKey, message, hmac);
        System.out.println("Message \""+message+"\" is "+((verify)?"valid":"invalid"));
         
        // HMAC SHA512 with modified message - verify == false
        message += "asdf";
        verify = HMAC.verify(HMAC.Algorithms.HmacSHA512, secretKey, message, hmac);
        System.out.println("Message \""+message+"\" is "+((verify)?"valid":"invalid"));
    }
}

Output from the example class:

Message "This is a test message" is valid
Message "This is a test messageasdf" is invalid