Skip to content

Instantly share code, notes, and snippets.

@Stexxen
Last active September 4, 2025 12:00
Show Gist options
  • Save Stexxen/90b5a4625d5e4e64d8a19137c6e0adca to your computer and use it in GitHub Desktop.
Save Stexxen/90b5a4625d5e4e64d8a19137c6e0adca to your computer and use it in GitHub Desktop.

Signing and Verifying using Public/Private Keys

The subject of Signing and Verifying messages in the frame of the CSF is based on the concept of a joined pair of keys, called the public and private key. The private key is kept secret while the public key being available to all. Both keys can encrypt data, but only the other key can decrypt that data.

This means that if you have some data that has been encrypted by a 3rd party, and you have their private key, and it decrypts correctly, then you know that the data definitely came from that 3rd party, as only they could have encrypted it. These concepts form the basics of Signing and Verifying. Further information can be acquired from https://en.wikipedia.org/wiki/Public-key_cryptography and https://en.wikipedia.org/wiki/Digital_signature

Domain Key Identified Messages (DKIM)

DKIM is an RFC Standard https://datatracker.ietf.org/doc/html/rfc6376 and sets out a specific method for using public/private keys to sign and verify messages. Effectively the sender creates a hash of the message using the rules set out in the RFC and then encrupts it using their private key. When the recipient receives the message with the signature, they follow the same method to create a hash of the message and then decrypt the signature to get the original hash. If those both values match then the recipient knows that the parts of the message used for the hash have not been altered and (2) that if definitely came from the sender.

High level guide of DKIM Signatures

From the RFC https://datatracker.ietf.org/doc/html/rfc6376#section-3.7 we have copied the following section and simplified it slightly

   body-hash    =  hash-alg (canon-body)
   data-hash    =  hash-alg (h-headers, D-SIG, body-hash)
   signature    =  sig-alg (d-domain, selector, data-hash)

   where:

   body-hash:  is the output from hashing the body, using hash-alg.

   hash-alg:   is the hashing algorithm specified in the "a" parameter.

   canon-body: is a canonicalized representation of the body, produced
               using the body algorithm specified in the "c" parameter,
               as defined in Section 3.4 and excluding the
               DKIM-Signature field.

   data-hash:  is the output from using the hash-alg algorithm, to hash
               the header including the DKIM-Signature header, and the
               body hash.

   h-headers:  is the list of headers to be signed, as specified in the
               "h" parameter.

   D-SIG:      is the canonicalized DKIM-Signature field itself without
               the signature value portion of the parameter, that is, an
               empty parameter value.

   signature:  is the signature value produced by the signing algorithm.

   sig-alg:    is the signature algorithm specified by the "a"
               parameter.
   d-domain:   is the domain name specified in the "d" parameter.

   selector:   is the selector value specified in the "s" parameter.

      NOTE: Many digital signature APIs provide both hashing and
      application of the RSA private key using a single "sign()"
      primitive.  When using such an API, the last two steps in the
      algorithm would probably be combined into a single call that would
      perform both the "a-hash-alg" and the "sig-alg".

You will note that the l-param from the RFC has been removed, as, although it may be useful on emails that are many Mb in size, it is not relevant for messages that at most will be a few hundred Kb.

CSF Headers

There are currently 2 HTTP Headers used, they are :

HTTP Header Use
X-CSF-SIGNATURE-DATESTAMP Stores the date+time the signature was created.
Uses the format yyyyMMddHHmmssS where S is a tenth of a second unit.
We shall use 202508121340391 as the example
X-CSF-SIGNATURE This stores the signature of the message being sent.

A sample X-CSF-SIGNATURE looks like this

a=rsa-sha256; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; bh=/CWGLVBT2gdyO5cudfAEUSF43KHLmlJjU/Nr2YNqkos=; h=X-CSF-SIGNATURE-DATESTAMP; b=4J4OGZfxiIetZP8WMMt56+MBqX0W/KdV9pAxzeS4PBC5rjmRtQc9PNMfkd0+9S3bOtr6k5HFzE7E8Q+RrCDIKgm4MNZJmCHUzpFu6NqzV5/zdhM0sR7YYP0QHGjYqgvF4b9+pgT58s2AehFRDXAqdnExJx5K1Hfj9896tj9AgEqUNbxa6VAdrAR4FY2zRpCnOQo7/XDt0DkCTPUrIiP3B1Mrj4WUeYOa2c2xbTK3w6IHmQ22+INEcbL8msW5iXg/jtY/YI/NQWgouXBJoGi99dZIi8VqKVd/YxXFElGRaAUJiP1vpPX1dlW5n4mUdS1o63rie28OHWSAgMfg7QPwlsM8f0W2yVAKidrRoO98IXxBwLBUBM54GN8cQRzSYURO5Mv3cv8a9Dt1GgiSRXc9iG8kYL2t//dX1+/lb0iS17rQPDr+iW5625fNcECkLu7YMANtmCCEF54eKlRRit7bhgy3hnWmjE9kV9kzpj/ZB3TMzE8CEEIiUsQ62ZjlfnJi;

It can clearly be seen that this is identical to the DKIM header that email uses. All the tags seen above are the same as the ones used in the RFC https://datatracker.ietf.org/doc/html/rfc6376#section-3.5 but here is a quick summary

Tag
a The algorithms used to create the hash and encryption
s The selector part of the domain, will always be the CPs ID
c The header/body Canonicalization Algorithms to be used. Always simple/simple for CSF HTTP Messages
d The hostname to use in conjunction with the selector to get the Public Key via DNS
h Header fields that should be used in creating the signature
bh The Base64 hash of the raw body using the algorithm specfified in a
b The Base64 signature data

Encrypting and Signing algorithms

At the present time DKIM only requires implementors to support 2 encryption algorithms and 1 hashing function. In the example above rsa-sha256 is used and means RSA is the encryption and signing algorithm and SHA256 is the has function. It has also become standard to also support SHA512 although this is not mentioned in the RFC. SHA1 was used in the early days of DKIM but has since been removed, as it is no longer secure.

The other encryption and Signing algorithm introduced in 2018 is Ed25519 in RFC https://datatracker.ietf.org/doc/html/rfc8463, now although this is newer it has 1 major benefit in that the public keys are a lot shorter.

RSA public TXT DNS entry

k=rsa; p=MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA6/RekuGAsET2Ir8E4K5BU74MBXebo7QwrOnSGirtNgRLDcwP2R84Eeum9BLxZtiumUrmO6ja7DrteSZhzOyC09KM3b22zV43bm7nv/FF40+KpTDQ/FZ32W838UIZGgHOyICk37hlPvnGrVPg7rT6HAKsaj5W0zbvO5vKDUOz2r3bvrWo79YSxtVfGQ1pHhhZYV2dL1C0uiADbz5L1Cu0qCBjqGJQbYq4ALdaKTnAm2NG/N9W2i3IWJUQIyiTr11qtBrrR8sj2YhYeph6GTsOXggpAWZnEHZz+LKzD4roVqUxkByLWRLB2I5Ju/xPPtTjCDakfvGlsiWl5BMAhn79p0mW6hzcA4DT7Iz+nSV8K3XMU4rxs0AcaafcBsKREmfbGz4K3ycWHZ/Yrxb+PqYHy7NSK76mHsAjuU8nuKYtZGIYHwT4friS2R3HtXJ+olLmdZ+XnkpdvEGDb+ZVv/G9vYp020LZQBBd0hp1Ez8a/3F7jRt8xUJ0ljstWjbq+m3bAgMBAAE=

Ed25519 public TXT DNS entry

k=ed25519; p=MCowBQYDK2VwAyEAqiCrokPReIFI1h4Jdp7RCRRVQ4Fltdyn429O4H2jYog=

So here is a list of the algorithms we should support as part of CSF

Encryption & Signing Hashing a value
RSA SHA-256 rsa-sha256
RSA SHA-512 rsa-sha512
Ed25519 SHA-256 ed25519-sha256
Ed25519 SHA-512 ed25519-sha512

This is a short list so shouldn't cause implementers any problems.

Canonicalization Algorithms, the c Tag

Due to Email servers potentially modifying the body and headers it was necessary to specify how the headers and body were used in the overall signature. For CSF HTTP Messages using simple/simple will provide the most straight-forward implementation, as it does not require any post-processing on the headers or body

Public Key Storage

The DKIM standard has a defined format for both the URL where the public key is stored and how it is stored.

Domain

The domain is formated as [s]._domainkey.[d], the s & d are the values in the X-CSF-SIGNATURE and the s is the CP ID of the source of the message. which from the signature above is 809b6e65-a6e7-40f6-8b52-04dd65b6fce1._domainkey.gplb-test.nowyoyo.net

The CP ID will also match an entry in the directory, which will define the valid d that the public key is held for the CP. Any request where the d does not match the directory entry must be rejected.

TXT record

The public key of the CP is encoded as Base64 in a TXT record. https://datatracker.ietf.org/doc/html/rfc6376#section-3.6.1

k=rsa; p=MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA6/RekuGAsET2Ir8E4K5BU74MBXebo7QwrOnSGirtNgRLDcwP2R84Eeum9BLxZtiumUrmO6ja7DrteSZhzOyC09KM3b22zV43bm7nv/FF40+KpTDQ/FZ32W838UIZGgHOyICk37hlPvnGrVPg7rT6HAKsaj5W0zbvO5vKDUOz2r3bvrWo79YSxtVfGQ1pHhhZYV2dL1C0uiADbz5L1Cu0qCBjqGJQbYq4ALdaKTnAm2NG/N9W2i3IWJUQIyiTr11qtBrrR8sj2YhYeph6GTsOXggpAWZnEHZz+LKzD4roVqUxkByLWRLB2I5Ju/xPPtTjCDakfvGlsiWl5BMAhn79p0mW6hzcA4DT7Iz+nSV8K3XMU4rxs0AcaafcBsKREmfbGz4K3ycWHZ/Yrxb+PqYHy7NSK76mHsAjuU8nuKYtZGIYHwT4friS2R3HtXJ+olLmdZ+XnkpdvEGDb+ZVv/G9vYp020LZQBBd0hp1Ez8a/3F7jRt8xUJ0ljstWjbq+m3bAgMBAAE=

Where k is the key type, and p is the public key in base64.

Worked Example

What follows are simplistic Low-Level worked examples of the DKIM process for signing and verifying. However, it is possible that developers are able to make use of Higher-Level libraries that already implement the DKIM RFC and remove a lot of the low level implementation.

i.e. https://github.com/apache/james-jdkim is a Java library that can be used with some minor modifications, thereby removing the need to implement your own. https://github.com/jstedfast/MailKit is .NET library doing the same.

Low-Level Java code to create the signature.

public String createSignature() throws Exception {
  byte[] httpBody = """
      {
        "test_field": "Test Data"
      }""".getBytes();

  String privateKeyPEM = "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";
  KeyFactory keyFactory = KeyFactory.getInstance("RSA");
  PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyPEM)));

  MessageDigest digest = MessageDigest.getInstance("SHA256");
  // Get the body hash, using simple canonicalization (2nd part of 'c') as per https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.3
  String bh = Base64.getEncoder().encodeToString(digest.digest(httpBody));
  // bh = IVIj2cQQOAapFmSJl6X0y6dQgKWhYHqQetWe9mWINNQ=

  // Assumes we are using a CP with ID 809b6e65-a6e7-40f6-8b52-04dd65b6fce1 that is hosted on the MAP Nowyoyo.
  String sigTemplate = "a=rsa-sha256; q=dns/txt; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=" + bh + ";";
  // sigTemplate = a=rsa-sha256; q=dns/txt; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=IVIj2cQQOAapFmSJl6X0y6dQgKWhYHqQetWe9mWINNQ=;

  // Normally we would parse the 'a' tag to get the correct algorithm, but this is a simple example.
  Signature signature = Signature.getInstance("SHA256withRSA");
  // Initialise with the Private Key
  signature.initSign(privateKey);
  // As we are using simple canonicalization (1st part of 'c'), we add the header value only
  signature.update("202412121340391".getBytes());
  // Each Header must be appended with \r\n as per https://datatracker.ietf.org/doc/html/rfc6376#section-3.7
  signature.update("\r\n".getBytes());
  // Append the signature using simple canonicalization (1st part of 'c') as per https://datatracker.ietf.org/doc/html/rfc6376#section-5.6
  signature.update(("DKIM-Signature:" + sigTemplate+ " b=").getBytes());

  // append the signature to the sigTemplate
  String finalSignature = sigTemplate + " b=" + Base64.getEncoder().encodeToString(signature.sign());
  // finalSignature = a=rsa-sha256; q=dns/txt; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=IVIj2cQQOAapFmSJl6X0y6dQgKWhYHqQetWe9mWINNQ=; b=rSnlux6S7feBZBEA3wl0X+XA6RfNq8MFnS2nMo0dgEMNmQguNhRIpzuPBTU/BgPmZA57IXF4eMN886rFC5Lo0yy5cuSZpHaVbI5gvf/1hX/SAMJfa2PAipn1/9+yeUj8ajnUYMsO6v1i6bx6LA5Gq+mJEBm4XRwA3Ps3wdgJ1bzTQ7s1L8+/iXci1OqieMdWHvdBi7uncBhpFUQdHVEYZ7zpzDjG59B2i1Z2BGFBEfcTF9FJPfJbJoabUuzT1iZlcW1s18tnZyav/4X3ZTM5YIVGYo355gtiT4YNEmz+mwFN/cOvRkE+X7ukFvNrXOfjNTtfBf2aGWOxMuSFX9UkHi74Vyi5/Snim9Qp1OrKCYRhb+6NaqXev4rbs244t7NTAy0D3WcWxWZN5tf6R8rQ1jm1LklaX2xtr7GaM07mPGRFvGGukT7VSzo1nxD9JDKTiP/C8cA5+kunIFXNrst6xXI+0GYC3QiPZymsbsYEcp06AyOH/QZjUoGO77lAbZKJ

  // The result is then added to the HTTP Request as Header X-CSF-SIGNATURE
  return finalSignature;
}

Low-Level Java code to verify the signature.

In this code section, it is assumed that the Public Key has already been acquired from the DNS entry in the signature and has been verified that it matches the details from the directory

  public boolean verifySignature(PublicKey publicKey, HttpHeaders httpHeaders, byte[] httpBody) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
    // Get Signature and Timestamp from header
    String signatureHeader = httpHeaders.getFirst("X-CSF-SIGNATURE");
    // pull out the headers stated in the 'h' tag, they are appended to the signature in the order they appear in 'h'
    // in this simplistic example we already know that there was only 1 field mentioned.
    String timestampHeader = httpHeaders.getFirst("X-CSF-SIGNATURE-DATESTAMP");
    // timestampHeader = 202412121340391

    // get the Base 64 decoded value of the 'b' tag
    byte[] signatureBytes = getSignatureBytes(signatureHeader);
    // get the signature line without the 'b' tag
    String signatureTemplate = unsignedSignature(signatureHeader);
    // signatureTemplate = a=rsa-sha256; q=dns/txt; c=simple/simple; s=809b6e65-a6e7-40f6-8b52-04dd65b6fce1; d=gplb-test.nowyoyo.net; v=1; h=X-CSF-SIGNATURE-DATESTAMP; bh=IVIj2cQQOAapFmSJl6X0y6dQgKWhYHqQetWe9mWINNQ=;

    MessageDigest digest = MessageDigest.getInstance("SHA256");
    // Get the body hash, using simple canonicalization (2nd part of 'c') as per https://datatracker.ietf.org/doc/html/rfc6376#section-3.4.3
    String bh = Base64.getEncoder().encodeToString(digest.digest(httpBody));
    // bh = IVIj2cQQOAapFmSJl6X0y6dQgKWhYHqQetWe9mWINNQ=

    // Check the bh is the same as the bh value in the signature. throw exception if not
    verifyBodyHash(signatureHeader, bh);

    // Normally we would parse the 'a' tag to get the correct algorithm, but this is a simple example.
    Signature signature = Signature.getInstance("SHA256withRSA");
    // Initialise with the public Key for Verification
    signature.initVerify(publicKey);
    // As we are using simple canonicalization (1st part of 'c'), we add the header value only
    signature.update(timestampHeader.getBytes());
    // Each Header must be appended with \r\n as per https://datatracker.ietf.org/doc/html/rfc6376#section-3.7
    signature.update("\r\n".getBytes());


    // Append the signature using simple canonicalization (1st part of 'c') as per https://datatracker.ietf.org/doc/html/rfc6376#section-5.6
    signature.update(("DKIM-Signature:" + signatureTemplate + " b=").getBytes());

    // simple true/ false result
    return signature.verify(signatureBytes);
  }

Error Codes

If the message fails signature check there are 2 possible error codes to use

Error Code Meaning
8101 PERM_FAIL - The message failed verification. Retrying will not resolve anything
8102 TEMP_FAIL - The message failed verification due to a temporary problem such as a DNS fail.

An 8102 error code is suggesting that a retry could resolve the message. It does not specify when the retry should take place.

// Example of PERM_FAIL where the source has attempted to sign from a domain that is not mentioned in the directory for that CP.
{
  "errorText": "Domain gplb-test.nowyoyo.net is not valid key source for CP 809b6e65-a6e7-40f6-8b52-04dd65b6fce1",
  "errorCode": 8101
}
// Example of PERM_FAIL where the body hash does not match. There is no point testing the signature further
{
  "errorText": "Body Hash 3hzlA9zA8SAVapmd5ZZnMdwaZpk/WOyQXixrqVu/WMc= is different to signature /CWGLVBT2gdyO5cudfAEUSF43KHLmlJjU/Nr2YNqkos=",
  "errorCode": 8101
}
// Example of PERM_FAIL where the Signature header X-CSF-SIGNATURE does not contain the mandatory tags
{
  "errorText": "Signature has missing mandatory tag(s): [b, bh]",
  "errorCode": 8101
}
// Example of TEMP_FAIL where the URL calculated from the X-CSF-SIGNATURE does not have a TXT value.
{
  "errorText": "Unable to resolve 809b6e65-a6e7-40f6-8b52-04dd65b6fce1._domainkey.gplb-test.nowyoyo.net. TXT entry does not exist.",
  "errorCode": 8102
}
// Example of TEMP_FAIL where the URL calculated from the X-CSF-SIGNATURE contains a TXT value, but unable to convert it into a public key.
{
  "errorText": "Unable to resolve 809b6e65-a6e7-40f6-8b52-04dd65b6fce1._domainkey.gplb-test.nowyoyo.net. Cannot get public key",
  "errorCode": 8102
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment