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:
- Private key signs a hash of the email content
- Public key (published in DNS) verifies the signature
- 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:
| Tag | Purpose |
|---|---|
| `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.
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:
| Language | Library |
|---|---|
| Node.js | `nodemailer` (built-in DKIM support) |
| Python | `dkim` package |
| Go | `go-msgauth` |
| PHP | PHPMailer with DKIM |
Debugging DKIM Failures
Step 1: Check the Signature
Extract the DKIM-Signature header and verify:
d=matches your domains=selector exists in DNSh=includes required headers (especiallyfrom)
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:
- Check if intermediate servers modify content
- Try
simple/simplecanonicalization to identify changes - 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:
- Generate test keys with DKIM Creator
- Configure local mail server (Postfix) with the private key
- Add public key to local DNS (or mock resolver)
- 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
simpleis fragile—userelaxed/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:
Generate keys at deployment
Generate keys during setup. Store private key securely (environment variable, secrets manager).
Configure mail library
Use your mail library's DKIM support. Pass the private key, domain, and selector.
Publish DNS record
Add the public key to DNS. Document the selector for operators.
Monitor delivery
Set up DMARC reporting to catch signature failures.
Related Articles
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