API Authentication: HMAC with SHA-256
Overview
Hash-based Message Authentication Code (HMAC) with SHA-256 cryptographic hashing is a common security mechanism used for Authentication and Authorization when sending requests to secure APIs. The process involves both encryption and hashing elements. To ensure that Skipify remains secure, we utilize MAC with SHA-256 for most Skipify API requests.
Required Headers for HMAC with SHA-256 Requests
API calls that utilize HMAC with SHA-256 authentication require a set of 4 headers to be sent along with the request:
- x-merchant-id
- timestamp
- nonce
- signature
The first three headers will be used to generate the string for the signature as shown below.
Header Element | Description | Example Value |
---|---|---|
X-Merchant-Id | This is a GUID (Globally Unique Identifier) that was provided to you during onboarding by the Skipify team. You can also view this identifier by logging into the merchant portal. | 76aae15d-de06-46df-91c8-3ff5beca1c8d |
Timestamp | This is the current timestamp in Unix Epoch time (s). | 1616562172 |
Nonce | A nonce is a unique identifier to protect private communications by preventing replay attacks. Nonces are random or pseudo-random numbers/strings that authentication protocols attach to communications. This is a string set by you that must be unique for each request. | 51c1442ebe284b74814cbc8411502b7c |
Signature | The signature is a hashed string containing both your Skipify merchant credentials, and elements of the specific request. Below are additional Instructions for generating the signature. | 8bd00d630eea81f7ec1243cc0e2bc40516ecc3b91d863ba5d398d19aa294c2d8 |
Generating the Signature
Signatures will be generated from a string containing the x-merchant-id, x-api-key, timestamp, nonce, requestUri, requestMethod,
and requestBody
. Descriptions for these elements are listed below:
String Element | Description | Example Value |
---|---|---|
X-Merchant-Id | This is a GUID (Globally Unique Identifier) that was provided to you during onboarding by the Skipify team. You can also view this identifier by logging into the merchant portal. | 76aae15d-de06-46df-91c8-3ff5beca1c8d |
X-Api-Key | Your API Key that was provided to you during onboarding by the Skipify team. You can view your key in the Configuration page of the merchant portal. | f51fa8fc7b2d55689c21009ab3ffcbc4 |
Timestamp | This is the current timestamp in Unix Epoch time (s). | 1616562172 |
Nonce | A nonce is a unique identifier to protect private communications by preventing replay attacks. Nonces are random or pseudo-random numbers/strings that authentication protocols attach to communications. This is a string set by you that must be unique for each request. | 51c1442ebe284b74814cbc8411502b7c |
RequestURI | This is the request path. If performing a GET request, the query string must be included. Note: The query string parameters must be in alphabetical order and the parameter values must be URL encoded using UTF-8 format like the example shown. | orders/e40b83b7-4c5e-47e9-b6a7-c005831eb1d8/capture payment-requests?begin=2022-02-02t21%3a21%3a21z&end=2022-02-02t21%3a21%3a21z&pageNumber=1&pageSize=25 Note: do not include a leading or trailing forward slash, "/", in the URI |
RequestMethod | Method associated with the request. | GET | POST | PUT | DELETE |
RequestBody | Body of the request (empty for GET requests). See specific documentation for the required body for each endpoint. | { "object": { "a": "b", "c": "d", "e": "f" }, "array": [ 1 2 ], "string": "Hello World" } |
The string should be formatted as follows:
[x-merchant-id]|[x-api-key]|[Timestamp]|[Nonce]|[RequestURI]|[RequestMethod]|[RequestBody]
Using the example data from the table above, the generated string will be as follows:
POST:
76aae15d-de06-46df-91c8-3ff5beca1c8d|f51fa8fc7b2d55689c21009ab3ffcbc4|1616562172|51c1442ebe284b74814cbc8411502b7c|orders/e40b83b7-4c5e-47e9-b6a7-c005831eb1d8/capture|POST|{"object":{"a":"b","c":"d","e":"f"},"array":[1,2],"string":"Hello World"}
GET:
76aae15d-de06-46df-91c8-3ff5beca1c8d|f51fa8fc7b2d55689c21009ab3ffcbc4|1616562172|51c1442ebe284b74814cbc8411502b7c|payment-requests?begin=2022-02-02t21%3a21%3a21z&end=2022-02-02t21%3a21%3a21z&pageNumber=1&pageSize=25|GET|
Once the string has been generated, it should be formatted, encoded, and hashed following the steps below:
- Remove all whitespace from the string (spaces, line feeds, carriage returns, and tabs should be replaced with an empty string)
- Convert string to uppercase
- Encode the string with Base64
- Generate a SHA-256 hash from the Base64 encoded string
Generating the Signature Examples
The following are code examples, all of which generate the HMAC with SHA256 signature. These methods take in the parameters (X-Merchant-Id, X-Api-Key, Timestamp, Nonce, RequestURI, RequestMethod, RequestBody
) and return a value to be used in the request signature header.
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class HeaderUtil
{
/*
Generate the MAC with SHA-256 signature using the necessary keys and HTTP elements
<param name="merchantId">The provided MID (e.g., "00000000-0000-0000-0000-000000000000") </param>
<param name="apiKey">The provided API key (e.g., "00000000-0000-0000-0000-000000000000")</param>
<param name="timestamp">The current Unix Epoch time (in seconds) (e.g., "1634324248" )</param>
<param name="nonce">Unique Request Identifier (e.g., "00000000-0000-0000-0000-000000000000")</param>
<param name="uriPath">Path of the request URI (e.g., "items/1")</param>
<param name="httpVerb">HTTP verb of the request (e.g., "POST")</param>
<param name="body">Body of the request(e.g., "{ "field1": "value1" }")</param>
<returns> The signature header for MAC with SHA-256 requests </returns> */
public String macSignature(String merchantId, String apiKey, String timestamp, String nonce, String uriPath,
String httpVerb, String body)
throws NoSuchAlgorithmException
{
String formattedSignature = merchantId + "|" + apiKey + "|" + timestamp
+ "|" + nonce + "|" + uriPath + "|" + httpVerb + "|" + body;
//remove all whitespace and convert to uppercase
formattedSignature = formattedSignature.replaceAll("[\\r\\n\\t\\s]", "").toUpperCase();
//base 64 encode
String base64EncodedString = Base64.getEncoder().encodeToString(formattedSignature.getBytes());
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch(NoSuchAlgorithmException e) {
System.out.println("Could not find algorithm");
}
byte[] sha256Hash = digest.digest(base64EncodedString.getBytes(StandardCharsets.UTF_8));
return convertBytesToHex(sha256Hash);
}
private static String convertBytesToHex(byte[] hash)
{
StringBuilder hexString = new StringBuilder(2 * hash.length);
for (byte b : hash)
{
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1)
{
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace MAC {
public class HeaderUtility {
/// <summary>
/// Generate the MAC signature with SHA-256 using the necessary keys and HTTP elements
/// </summary>
/// <param name="merchantId">The provided MID (e.g., "00000000-0000-0000-0000-000000000000") </param>
/// <param name="apiKey">The provided API key (e.g., "00000000-0000-0000-0000-000000000000")</param>
/// <param name="timestamp">The current Unix Epoch time (in seconds) (e.g., "1634324248" )</param>
/// <param name="nonce">Unique Request Identifier (e.g., "00000000-0000-0000-0000-000000000000")</param>
/// <param name="uriPath">Path of the request URI (e.g., "items/1")</param>
/// <param name="httpVerb">HTTP verb of the request (e.g., "POST")</param>
/// <param name="body">Body of the request(e.g., "{ "field1": "value1" }")</param>
/// <returns>The signature header for MAC with SHA-256 requests </returns>
public static string GenerateSignature(string merchantId, string apiKey, string timestamp, string nonce, string uriPath, string httpVerb, string body) {
// Compose signature elements
string signatureRaw = $"{merchantId}|{apiKey}|{timestamp}|{nonce}|{uriPath}|{httpVerb}|{body}";
// Remove characters CR, LF, TAB, and whitespace
string signatureCleaned = Regex.Replace(signatureRaw, @"[\r\n\t\s]", string.Empty);
// Convert string to upper-case
string signatureUpper = signatureCleaned.ToUpperInvariant();
// Base64 encode string
string signatureBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(signatureUpper));
// SHA-256 hashed string
using SHA256 sha256 = SHA256.Create();
byte[] signatureHashedBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(signatureBase64));
string signature = string.Join(null, signatureHashedBytes.Select(b => b.ToString("x2")));
return signature;
}
}
}
<?PHP
/**
* Generate the MAC with SHA-256 signature using the necessary keys and HTTP elements
* @param string $merchant_id The provided MID (e.g., "00000000-0000-0000-0000-000000000000")
* @param string $api_key The provided API key (e.g., "00000000-0000-0000-0000-000000000000")
* @param integer $timestamp The current Unix Epoch time (in seconds) (e.g., "1634324248"
* @param string $nonce Unique Request Identifier (e.g., "00000000-0000-0000-0000-000000000000")
* @param string $uri_path Path of the request URI (e.g., "items/1")
* @param string $http_verb HTTP verb of the request (e.g., "POST")
* @param string $body Body of the request(e.g., "{ "field1": "value1" }")
* @return string The signature header for MAC with SHA-256 requests
*/
function mac_signature($merchant_id, $api_key, $timestamp, $nonce, $uri_path, $http_verb, $body)
{
// Compose signature elements
$signature_raw = "{$merchant_id}|{$api_key}|{$timestamp}|{$nonce}|{$uri_path}|{$http_verb}|{$body}";
// Remove characters CR, LF, TAB, and whitespace
$signature_cleaned = str_replace(array("\r", "\n", "\t", " ") , '', $signature_raw);
// Convert string to upper-case
$signature_upper = strtoupper($signature_cleaned);
// Base64 encode string
$signature_base64 = base64_encode($signature_upper);
// SHA-256 encode string
$signature = hash('sha256', $signature_base64);
return $signature;
}
?>
Updated 7 months ago