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:
-
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
tis the UNIX epoch timestamp in seconds, andv1contains the signature(s). You can discard all other elements. -
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 headertwhen 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 } -
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. -
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); } } -
Compare signatures
Compare the generated signatures with all signatures in the header. At least one must match, otherwise the request is invalid.