Hmac example (java)

Example code:

/*
* Copyright (c) 2017 LINK Mobility Oy. All rights reserved.
*/

package com.labyrintti.silverbullet.payment;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.UUID;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

/**
* <p>
* Authorization header for the HTTP requests to the LINK Payment API. Such header contains:
* <ul>
* <li>payment credentials identifying a Payment servie's partner</li>
* <li>information about the request: HTTP method, URL and payload(body)</li>,
* <li>unique identifiers for the header: timestamp and UUID</li>
* </ul>
* .
* </p>
*
*/
public class AuthorizationHeader {

private static final String HEADER_NAME = "Authorization";
/**
* The encoding used for the header
*/
private static final String ENCODING = "UTF-8";

/**
* Identifier for the MD5 algorithm as expected from {@link MessageDigest java.security.MessageDigest}
*/
private static final String MD5_ALGORITHM_NAME = "MD5";

/**
* Identifier for the HmacSHA256 algorithm as expected from {@link javax.crypto.Mac}
*/
private static final String HMAC_SHA256_ALGORITHM_NAME = "HmacSHA256";

/**
* The number of characters from the signature used in the header
*/
private static final int HEADER_SIGNATURE_LENGTH = 10;

/**
* The symbol used to separate the parts of the header's value
*/
private static final String HEADER_PARTS_SEPARATOR = ":";

/**
* A prefix used for the header's value
*/
private static final String HEADER_PREFIX = "hmac ";

/**
* A partner ID used to identify the partner
*/
private Integer partnerId;

/**
* An authentication secret key for the partner
*/
public String secretKey;

/**
* UNIX time (the number of seconds since Epoch UTC (January 01, 1970)) used to identify a request
*/
private long timestampInSeconds;

/**
* A UUID used to identify a request
*/
private UUID uuid;

/**
* The HTTP method of the request
*/
private String httpMethod;

/**
* The absolute URL of the request including any query parameters
*/
private String url;

/**
* The payload(body) of the request
*/
private String requestPayload;

/**
* Constructs AuthorizationHeader with the provided data and internally generated identifiers - timestamp (the
* current date) and UUID
* 
* @param partnerId the id of the Payment service's partner
* @param secretKey the secret key used for authentication
* @param httpMethod the HTTP method of the request
* @param url the absolute URL of the request including any query parameters
* @param requestPayload the payload(body) of the request; the payload is not a required value and both null and
* empty string are accepted
*/
public AuthorizationHeader(Integer partnerId, String secretKey, String httpMethod, String url, String requestPayload) {
super();
this.partnerId = partnerId;
this.secretKey = secretKey;
this.httpMethod = httpMethod;
this.url = url;
this.requestPayload = requestPayload;

initHeaderIdentifiers();
}

/**
* Returns the authorization header value for the Payment API and returns it. The header value should be in follow
* the pattern: "hmac {partnerId}:{signature}:{nonce}:{timestamp}" where:
* <ul>
* <li>{partner} the partner id</li>
* <li>{signature} the first 10 characters of a Hmac Sha256 hash used as signature</li>
* <li>{nonce} a UUID that is unique for each header</li>
* <li>{timestamp} the partner id</li>
* 
* @return the value of the authorization header
* @throws AuthorizationHeaderValueConstructionException in case the header value cannot be constructed
*/
public String getHeaderValue() throws AuthorizationHeaderValueConstructionException {
String headerValue = null;
try {
headerValue = constructHeaderValueFromSourceData();
} catch (InvalidKeyException | UnsupportedEncodingException | NoSuchAlgorithmException e) {
throw new AuthorizationHeaderValueConstructionException(e);
}
return headerValue;
}

/**
* Returns the name of the authorization header's name
* 
* @return the name of the authorization header
*/
public String getHeaderName() {
return HEADER_NAME;
}

/**
* Constructs the authorization header value for the Payment API from the header's source data
* 
* @return the value of the authorization header
* @throws UnsupportedEncodingException in case the {@link #ENCODING header's encoding} is not supported
* @throws NoSuchAlgorithmException in case any of the hashing algorithms used is not supported
* @throws InvalidKeyException in case the privateSecretKey is inappropriate
*/
private String constructHeaderValueFromSourceData() throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
String message = generateMessageToBeSigned();
String signature = sign(message);
String header = buildAuthorizationHeaderString(signature);

return header;
}

/**
* Initiates the values of the values that identify a concrete header: it's timestamp and UUID
*/
private void initHeaderIdentifiers() {
this.timestampInSeconds = new Date().getTime() / 1000;
this.uuid = UUID.randomUUID();
}

/**
* Generates a string (called "message") used to create signature for the header. This message is constructed from
* the request data, the {@link #partnerId partner ID}, the {@link #timestampInSeconds} and the {@link #uuid}
* 
* @return a message used to create a header's signature
* @throws UnsupportedEncodingException in case the {@link #ENCODING} is not supported by the URL encoder or
* string-to-byte encoder
* @throws NoSuchAlgorithmException in case the hashing algorithm is not supported
*/
private String generateMessageToBeSigned() throws UnsupportedEncodingException, NoSuchAlgorithmException {
String upperCaseHttpMethodName = httpMethod.toUpperCase();
String timestampAsString = Long.toString(timestampInSeconds);
String lowerCaseUrl = url.toLowerCase();
String urlEncodedLowerCaseUrl = URLEncoder.encode(lowerCaseUrl, ENCODING);
String requestPayloadEncodedHash = StringUtils.EMPTY;
if (requestPayload != null && !StringUtils.isEmpty(requestPayload)) {
requestPayloadEncodedHash = getHashedAndEncodedRequestPayload();
}

String messageToBeSigned = partnerId + upperCaseHttpMethodName + urlEncodedLowerCaseUrl + timestampAsString + uuid + requestPayloadEncodedHash;

return messageToBeSigned;
}

/**
* Creates a HMAC signature string for the header
* 
* @param message an input message to be signed
* @return the provided message hashed with HMAC-SHA256 using the {@link #privateSecretKey}
* @throws UnsupportedEncodingException in case the charset used to encode the message into a byte array
* @throws InvalidKeyException in case the {@link #privateSecretKey} is inappropriate for MAC
* @throws NoSuchAlgorithmException in case the selected MAC algorithm (HMAC-SHA256) is not supported
*/
private String sign(String message) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
byte[] decodedSecretKeyByteArray = getDecodedSecretKeyAsByteArray();
byte[] messageByteArray = message.getBytes(ENCODING);

byte[] hash = createSha256Hash(decodedSecretKeyByteArray, messageByteArray);
String encodedHash = Base64.encodeBase64String(hash);

return encodedHash;
}

/**
* Builds a authorization header value using part of the provided signature by joining all the header parts (the
* {@link #partnerId partner ID}, the first characters of the signature, the {@link #uuid} and the
* {@link #timestampInSeconds}) separated by {@link AuthorizationHeader#HEADER_PARTS_SEPARATOR} and adding a
* {@link #HEADER_PREFIX prefix} to the string
* 
* @param fullSignature the full signature for the header
* @return an authorization header that uses the provided signature
*/
private String buildAuthorizationHeaderString(String fullSignature) {
String timestampAsString = Long.toString(timestampInSeconds);
String headerSignature = fullSignature.substring(0, HEADER_SIGNATURE_LENGTH);
String partnerIdAsString = String.valueOf(partnerId);
String uuidString = uuid.toString();

String joinedHeaderParts = joinStringsWithSeparator(HEADER_PARTS_SEPARATOR, partnerIdAsString, headerSignature, uuidString, timestampAsString);
String headerValue = HEADER_PREFIX + joinedHeaderParts;
return headerValue;
}

/**
* Hashes and encodes the request payload (body) using MD5 and Base64 respectively
* 
* @return the hashed and then encoded payload
* @throws NoSuchAlgorithmException in case the hash algorithm is not supported
* @throws UnsupportedEncodingException in case the encoding used for th header is not supported
*/
private String getHashedAndEncodedRequestPayload() throws NoSuchAlgorithmException, UnsupportedEncodingException {
byte[] requestPayloadHash = createMd5Hash(requestPayload);
String requestPayloadEncodedHash = Base64.encodeBase64String(requestPayloadHash);

return requestPayloadEncodedHash;
}

/**
* Returns the secret key decoded with Base64
* 
* @return the decoded secret key
*/
private byte[] getDecodedSecretKeyAsByteArray() {
byte[] decodedSecretKey = Base64.decodeBase64(secretKey);

return decodedSecretKey;
}

/**
* Creates a MD5 hash from the provided input
* 
* @param input the input string to hash
* @return the creates hash as binary data
* @throws NoSuchAlgorithmException in case the selected algorithm (MD4) is not supported
* @throws UnsupportedEncodingException in case the encoding of the input character set is not supported
*/
private static byte[] createMd5Hash(String input) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md5Digest = MessageDigest.getInstance(MD5_ALGORITHM_NAME);
md5Digest.update(input.getBytes(ENCODING));
byte[] hash = md5Digest.digest();

return hash;
}

/**
* Creates HmacSHA256 hash of a message using the provided secret key
* 
* @param secretKey a secret key used to create the hash
* @param message the message to be hashed and authenticated/validated later
* @return the created hash as binary data
* @throws NoSuchAlgorithmException in case the selected algorithm (HmacSHA256) is not supported
* @throws InvalidKeyException in case the provided key is inappropriate for the MAC
*/
private static byte[] createSha256Hash(byte[] secretKey, byte[] message) throws NoSuchAlgorithmException, InvalidKeyException {
Mac algorithm = Mac.getInstance(HMAC_SHA256_ALGORITHM_NAME);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, HMAC_SHA256_ALGORITHM_NAME);
algorithm.init(secretKeySpec);
byte[] hash = algorithm.doFinal(message);

return hash;
}

/**
* <p>
* Joins the elements of the provided array into a single String containing the provided list of elements.
* </p>
*
* @see StringUtils#join(Object[], String)
* @param separator the separator to use, null treated as ""
* @param stringsToJoin the varargs strings providing the values to join together. null elements are treated as ""
* @return the joined String, {@code null} if no strings to join are provided
*/
private static String joinStringsWithSeparator(String separator, String... stringsToJoin) {
String joined = StringUtils.join(stringsToJoin, separator);
return joined;
}

}


Feeling lost? Click on this link! Portal page