Skip to main content

How to create message signatures

This guide will show you how to implement message signatures for API requests. Message signatures allow us to verify that API requests come from a valid source and ensure the integrity of each request.

There are two steps to creating a message signature:

  1. Create a key pair: only users with the admin role can do this.
  2. Sign a message: anyone sending API requests can do this.

Create a key pair

caution

Only users with the admin role can add public keys.

Message signatures use asymmetric encryption, which requires a pair of keys:

  • A private key to generate signatures that are attached to each request.
  • A public key shared with Griffin and used to validate the signature of each request.

Create a public and private key

You can generate keys using the openssl command line tool, or a cryptographic library such as Bouncy Castle.

Generate a private and public key, using the ed25519 algorithm.

openssl genpkey -algorithm ed25519 -out private.pem -outpubkey public.pem
note

You can use any filename for the keys, but using the .pem extension is recommended.

Add the public key via the app

  1. Log in to app.griffin.com using an account with the admin role.
  2. Navigate to Settings > Message signatures and select Add public key.
  3. Complete the public key form, which includes:
    • Title (optional): give your key a human readable name.
    • SHA: an exact copy of the contents of the public key file, as shown in the example below.
-----BEGIN PUBLIC KEY-----
MCxwxQYxKxVwAxxAxTxD1x2xcxExOxxxatx/JxWxVxYxxxVzxxxkxLkxVxw=
-----END PUBLIC KEY-----

Griffin will generate a unique keyid for each public key. Use this keyid in the signature base when generating message signatures.

info

You must use at least one key pair per organization. Do not use this key pair with any other service or platform. We recommend these security practices for managing your private keys.

View your public keys

You can view all public keys added to your organization on the Message signatures page in the app.

For each public key, you can view:

  • Title: optional name for the key
  • keyid : unique identifier generated by Griffin
  • SHA: the hash value
  • Added by: the user who added the key and the date it was added

Message Signatures Page on Griffin App

Sign a message

You can implement message signatures either with a programming language which has cryptographic libraries, or by calling out to openssl command line tooling.

Create a digest of the request body

The content-digest is the hash value of the request message body. The content-digest should be included in the signature-base to ensure the body cannot be tampered with.

  1. Generate a SHA-512 digest of the request body.
  2. Add the content-digest header to the request.
  3. Include the content-digest in the signature base.
"content-digest": sha-512=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:

Here are some suggested message content digest libraries.

Generate a unique request ID (nonce)

The nonce is a unique value that provides extra security for the request. The nonce should be included in the signature-base.

  1. Generate a nonce value using 128-bit UUID version 4 (random), variant 1.
  2. Include the nonce in the signature base.
nonce=a68aaca6-776c-44a3-adfd-37d7d5036f92

Create a signature base

A signature base defines the components of the message to be included when generating the signature.

To create a signature base:

  1. Add an alg parameter to the signature base.

This can only be the ed25519 algorithm.

alg="ed25519"
  1. Generate a signature created timestamp

    Use the current time, formatted as a Unix timestamp.

created=1618884479
  1. Set the expires timestamp

The signature should have an expiration time no greater than 300 seconds (five minutes) after signature created time.

expires=1618885579
  1. Add a keyId parameter to the signature base

Use the keyId value associated with your public key.

keyid="hs.123456abcdef"
  1. Assemble all components into the signature base.

Add the standard component for an http request message, and add the signature parameters in alphabetical order.

"@signature-params": ("@authority" "content-digest" "content-length" "content-type"
"date" "@method" "@path" "@query" "@target-uri");
alg="ed25519";
created=1724311276;
expires=1724311576;
keyid="hs.123456abcdef";
nonce="019178f6-a7f5-4edb-9ddc-b1488ed84af9"
info

Components in the signature base are included in the signature-input header of the signed request message.

Generate the message signature and attach headers

You can now generate a message signature based on your signature base, either by using a programming language with cryptographic library or calling out to the openssl command.

Attach the signature to the request message using these headers:

  • The content-digest header verifies the body of the request using SHA-512.
  • The signature header attaches the signature value generated from the signature base to the request message using a signature label name.
  • The signature-input header should use the same signature label name, and lists components from the signature base.
"content-digest": sha-512=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:

signature: unique-label=:Z1cxalMURtkA94LnchyyFASdEFH1+IY5joho7FhXjVPAhzyImT6NWqSV/vc+KBIPFM/JwJbGk/7k69VGIMcyAg==:

signature-input:
unique-label=("@authority" "content-digest" "content-length" "content-type"
"date" "@method" "@path" "@query" "@target-uri");
alg="ed25519";
created=1724311276;
expires=1724311576;
keyid="hs.123456abcdef";
nonce="019178f6-a7f5-4edb-9ddc-b1488ed84af9""
info

A request message can include a multiple signatures. Each signature uses a unique signature label to match the relevant signature-input and signature header values.

Using multiple signatures is useful when rotating cryptographic keys.

Test your implementation

Use the /v0/security/message-signature/verify endpoint to test your signed messages. If unsuccessful, the endpoint will provide feedback to help you troubleshoot.

Once message signatures are correctly generated, they can be used with all other Griffin API endpoints.

A successful response looks like this:

Status Code: 200

Response Headers:
Date: Mon, 23 Sep 2024 08:52:47 GMT
Content-Type: application/json;charset=utf-8
Content-Length: 59
Server: Jetty(9.4.53.v20231009)

Response Body:
{"busy":true,"message":"Your call is very important to us"}

Signature Verification Result:
Label: sig1
Algorithm: <class 'http_message_signatures._algorithms.ED25519'>
Covered Components:
"@method": POST
"@authority": localhost:8080
"@path": /v0/security/message-signature/debug
"content-type": application/json
"content-length": 18
"date": Mon, 23 Sep 2024 08:52:47 GMT
"content-digest": sha-512=:WZDPaVn/7xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==:
"@signature-params": ("@method" "@authority" "@path" "content-type" "content-length" "date" "content-digest");created=1727081567;keyid="acme-corp-2024"
Parameters:
created: 1727081567
keyid: test-key-ed25519

Signature verified successfully!

Example response if an invalid keyid is used to sign the message:

Status Code: 401
Response Body:
{"reject":"unknown-signing-key-id","details":"Unknown signing key"}

Example response if one of the components is missing, e.g the date component:

Status Code: 401
Response Body:
{"reject":"missing-required-components","details":["content-digest","date"]}

Reference

Cryptographic libraries

While we don't endorse specific libraries, here are some options to consider:

Always evaluate libraries thoroughly before integrating them into your project.

Message component definitions

alg is the the algorithm used to create the signature. This is always ed25519 as mandated by Griffin.

keyid is the unique id for your public key. It is generated by Griffin when you add a public key to your organization via the app.

content-digest is the SHA-512 hash of a message body.

content-length indicates the size of the message body in bytes.

content-type specifies the type of content sent to the API endpoint, e.g. application/json.

created shows the timestamp for when the signature was created.

date shows the date stamp of the request message.

expires is a time stamp which is a maximum of 300 seconds (five minutes) after the created time stamp.

nonce is a unique request id as an 128-bit UUID value, e.g. 6e5a16e1-c8c6-42da-b367-644d82360e8c.

@authority is the host header, e.g. api.griffin.com.

@method is POST, GET, etc.

@path is the API endpoint being called.

@target-uri is the request address in full, host, path, and query parameters.

Message content digest libraries

The following library provides SHA-512 hash encoding for the request message body.