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