DKIM for Developers: Understanding, Testing & Debugging

Learn DKIM implementation hands-on. Generate test keys, understand the cryptography, debug signature failures, and build email authentication into your applications.

Last updated: 2026-02-04

Understanding DKIM helps you build reliable email functionality, debug delivery issues, and implement proper authentication in your applications.

DKIM Creator generates keys in your browser using the Web Crypto API. It's a practical tool for learning how DKIM keys work and for generating test keys.

DKIM Fundamentals

At its core, DKIM is straightforward:

  1. Private key signs a hash of the email content
  2. Public key (published in DNS) verifies the signature
  3. Selector tells receivers where to find the public key

The signature covers specific headers (From, To, Subject, Date) and the body, creating a verifiable chain.

The DKIM Signature

Here's what a DKIM signature looks like in email headers:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
  d=example.com; s=selector;
  h=from:to:subject:date:message-id;
  bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
  b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB
    4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut
    KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV
    4bmp/YzhwvcubU4=

Key tags:

TagPurpose
`v=1`DKIM version
`a=rsa-sha256`Algorithm (RSA key, SHA-256 hash)
`c=relaxed/relaxed`Canonicalization (header/body)
`d=example.com`Signing domain
`s=selector`Key selector
`h=...`Signed headers
`bh=...`Body hash (base64)
`b=...`Signature (base64)

Canonicalization Deep Dive

Canonicalization normalizes email content before signing/verifying. Two modes:

Simple:

  • Headers: No changes
  • Body: Trailing whitespace only

Relaxed:

  • Headers: Lowercase names, unfold lines, compress whitespace
  • Body: Same as simple plus whitespace compression

Use relaxed/relaxed (header/body) for maximum compatibility—emails often get modified in transit.

Generate test keys

Create DKIM keys to experiment with signature verification and debugging.

Generate Keys

Generating Keys Programmatically

While DKIM Creator handles key generation in the browser, here's how to do it in code:

Node.js

const crypto = require('crypto');

const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem'
  },
  privateKeyEncoding: {
    type: 'pkcs8',
    format: 'pem'
  }
});

// Extract base64 for DNS record
const publicKeyBase64 = publicKey
  .replace('-----BEGIN PUBLIC KEY-----', '')
  .replace('-----END PUBLIC KEY-----', '')
  .replace(/\n/g, '');

console.log(`v=DKIM1; k=rsa; p=${publicKeyBase64}`);

Python

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import base64

private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
)

public_key = private_key.public_key()

# Get DER format for DNS
public_der = public_key.public_bytes(
    encoding=serialization.Encoding.DER,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

public_b64 = base64.b64encode(public_der).decode('ascii')
print(f"v=DKIM1; k=rsa; p={public_b64}")

OpenSSL (Command Line)

# Generate private key
openssl genrsa -out private.pem 2048

# Extract public key
openssl rsa -in private.pem -pubout -out public.pem

# Get base64 for DNS (remove headers/newlines)
openssl rsa -in private.pem -pubout -outform DER | base64 | tr -d '\n'

Signing Emails

Most applications use libraries for DKIM signing. Here's the concept:

// Pseudocode for DKIM signing
function signEmail(email, privateKey, domain, selector) {
  // 1. Select headers to sign
  const headersToSign = ['from', 'to', 'subject', 'date'];

  // 2. Canonicalize headers and body
  const canonicalHeaders = canonicalize(email.headers, 'relaxed');
  const canonicalBody = canonicalize(email.body, 'relaxed');

  // 3. Hash the body
  const bodyHash = sha256(canonicalBody);

  // 4. Build signature header template
  const sigTemplate = buildSignatureHeader({
    domain,
    selector,
    headers: headersToSign,
    bodyHash
  });

  // 5. Sign the canonical headers + signature template
  const signature = rsaSign(canonicalHeaders + sigTemplate, privateKey);

  // 6. Add signature to header
  return addHeader(email, 'DKIM-Signature', sigTemplate + signature);
}

Popular libraries:

LanguageLibrary
Node.js`nodemailer` (built-in DKIM support)
Python`dkim` package
Go`go-msgauth`
PHPPHPMailer with DKIM

Debugging DKIM Failures

Step 1: Check the Signature

Extract the DKIM-Signature header and verify:

  • d= matches your domain
  • s= selector exists in DNS
  • h= includes required headers (especially from)

Step 2: Verify DNS Record

# Query the public key
dig TXT selector._domainkey.example.com +short

# Verify it parses correctly
# Should show: v=DKIM1; k=rsa; p=...

Step 3: Check Key Match

Ensure the DNS public key matches your private key:

# Extract public key from private
openssl rsa -in private.pem -pubout | openssl pkey -pubin -outform DER | base64

# Compare with DNS record p= value

Step 4: Test Canonicalization

If signatures fail inconsistently:

  1. Check if intermediate servers modify content
  2. Try simple/simple canonicalization to identify changes
  3. Look for footer additions, encoding changes, or header rewrites

Step 5: Verify Body Hash

import hashlib
import base64

# Canonicalize body (relaxed: strip trailing whitespace per line, single trailing CRLF)
body = email_body.encode('utf-8')
body_hash = base64.b64encode(hashlib.sha256(body).digest()).decode()

# Compare with bh= value in signature

Testing Tools

Online validators:

  • mail-tester.com — Comprehensive email testing
  • mxtoolbox.com/dkim.aspx — DKIM record lookup
  • dkimvalidator.com — Signature verification

Command-line:

# Verify a DKIM signature (Python dkim package)
pip install dkim
dkimverify < email.eml

# Check DNS record exists
dig TXT selector._domainkey.example.com

Local testing setup:

  1. Generate test keys with DKIM Creator
  2. Configure local mail server (Postfix) with the private key
  3. Add public key to local DNS (or mock resolver)
  4. Send test emails and verify signatures

Common Implementation Pitfalls

1. Signing after modification

  • Sign emails as the last step before sending
  • Any change after signing breaks verification

2. Wrong canonicalization

  • simple is fragile—use relaxed/relaxed
  • Test with real email paths, not just local delivery

3. Missing headers in h= tag

  • Always include from
  • Include headers that shouldn't change (subject, date, message-id)

4. Key rotation issues

  • Keep old keys in DNS during transition (7+ days)
  • Update signing config before removing old DNS records

5. Body length issues

  • Avoid l= (length) tag—it allows body truncation attacks
  • Modern best practice is to sign the entire body

Building DKIM Into Applications

When your application sends email:

1

Generate keys at deployment

Generate keys during setup. Store private key securely (environment variable, secrets manager).

2

Configure mail library

Use your mail library's DKIM support. Pass the private key, domain, and selector.

3

Publish DNS record

Add the public key to DNS. Document the selector for operators.

4

Monitor delivery

Set up DMARC reporting to catch signature failures.


Experimenting with DKIM? Generate test keys instantly.

Generate DKIM keys for development

Create key pairs for testing and learning. Keys are generated locally in your browser.

Generate Keys