API Authentication: MAC w/ SHA-256

Overview

Message Authentication Code (MAC) 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. In order to ensure that Skipify remains secure, we utilize MAC with SHA-256 for most Skipify API requests. For example, all Order endpoints (Void, Refund, Capture) utilize MAC with SHA-256.

Required Headers for MAC with SHA-256 Requests

API calls that utilize MAC with SHA-256 authentication require a set of 4 headers to be sent along with the request: x-merchant-id, timestamp, nonce, and signature. The first three headers will be used to generate the string for the signature (see following section).
Header ElementDescriptionExample Value
X-Merchant-IdThis 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
TimestampThis is the current timestamp in Unix Epoch time (s).1616562172
NonceA 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
SignatureThe signature is a hashed string containing both your Skipify merchant credentials, and elements of the specific request.

Below are additional instructions about how to generate the signature.
8bd00d630eea81f7ec1243cc0e2bc40516ecc3b91d863ba5d398d19aa294c2d8

Generating the Signature

The signature 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 ElementDescriptionExample Value
X-Merchant-IdThis 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-KeyYour API Key that was provided to you during onboarding by the Skipify team. You can view your key by logging into the merchant portal.f51fa8fc7b2d55689c21009ab3ffcbc4
TimestampThis is the current timestamp in Unix Epoch time (s).1616562172
NonceA 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
RequestURIThis is the path of the request URI. In the example value, the path is bolded.orders/e40b83b7-4c5e-47e9-b6a7-c005831eb1d8/capture

Note: do not include a leading or trailing forward slash, "/", in the URI
RequestMethodMethod associated with the request.GET | POST | PUT | DELETE
RequestBodyBody 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 follow the following format:

[x-merchant-id]|[x-api-key]|[Timestamp]|[Nonce]|[RequestURI]|[RequestMethod]|[RequestBody]

Using the example data from the table above, the generated string would 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.

1. Remove all whitespaces from string (all spaces, line feeds, carriage returns, and tabs should be replaced with an empty string)

2. Convert string to uppercase

3. Base64 encode the string

4. Generate a sha256 hash from the Base64 encoded string

Generating the Signature Examples

The following are two code examples, both of which generate the MAC with SHA-256 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. One method is in Java and the other is in C#.

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 = MessageDigest.getInstance("SHA-256");
        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 with SHA-256 signature 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;
}
?>