prod - 20261103-155809 - acd260dde964f9fc12a4ec4d79828e2feffa7ef1

HMAC signature

All requests have a X-Seamly-Signature header containing a UNIX epoch timestamp in seconds as parameter t , and a scheme parameter v1 containing the signature. The signature is calculated with a secret, the timestamp and the request payload (note that for these GET requests, the request payload does not exist). The secret will be provided to you.

The header looks like this:

X-Seamly-Signature: t=1597999043,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

Seamly generates signatures using a hash-based message authentication code ( HMAC ) with SHA-256 . You should ignore all schemes that are not v1 .

It is possible to get multiple signatures with the same scheme in order to allow secret rotation and invalidation, one for each signature. An example header would then look like:

X-Seamly-Signature: t=1597999043,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08,
v1=90869314cc23cd66689f0d5cb78226255c41bfc60488c24ece99c2b16a1e538f

Signature verification

Verification should be done as follows:

  1. Extract timestamp and signatures from header

    Split the header, using the , character as the separator, to get a list of elements. Then split each element, using the = character as the separator, to get a key and value pair.

    The value for the key t is the UNIX epoch timestamp in seconds, and v1 contains the signature(s). You can discard all other elements.

  2. Verify timestamp

    The header contains a timestamp in order to prevent replay attacks . This timestamp cannot be modified as it is part of the signature. To verify the timestamp, get the current UNIX epoch timestamp in seconds on your machine ( tlocal ) and verify if the timestamp in the header t when converted to integer lies within a certain tolerance (5 min is recommended).

    A JavaScript example might be as follows:

    if (Math.abs(tlocal - t) < tolerance_in_s) {
      // Ok!
    } else {
      // This is either a replay attack or something is wrong with your or our server time
    }
  3. Create a payload string

    Concatenate the following:

    • The UNIX timestamp as string
    • A dot ( . )
    • The request body as string ( "" for GET requests)

    For example: 1597999043.

  4. Generate the expected signature(s)

    Generate the HMAC with SHA-256 hash function using the previously generated payload string and the configured secret. The output should be a hexadecimal string.

    In C# this would look like:

    using System;
    using System.Security.Cryptography;
    using System.Text;
    
    class MainClass {
      public static void Main (string[] args) {
        var secret = new ASCIIEncoding().GetBytes("the secret");
        var payload = new ASCIIEncoding().GetBytes("1597999043.");
        var hashmessage = new HMACSHA256(secret).ComputeHash(payload);
        string.Concat(Array.ConvertAll(hashmessage, x => x.ToString("x2")));
    
        // The expected signature:
        Convert.ToBase64String(hashmessage);
      }
    }

    In Java this would look like:

    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.NoSuchAlgorithmException;
    import java.security.InvalidKeyException;
    import javax.xml.bind.DatatypeConverter;
    
    class Main {
      public static void main(String[] args) {
        String secret = "the secret";
        String payload = "1597999043.";
        Mac hasher = Mac.getInstance("HmacSHA256");
        hasher.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
        byte[] hash = hasher.doFinal(payload.getBytes());
    
        // The expected signature:
        String signature = HexFormat.of().formatHex(hash);
      }
    }
  5. Compare signatures

    Compare the generated signatures with all signatures in the header. At least one must match, otherwise the request is invalid.