DKIM (DomainKeys Identified Mail)¶
Understanding DKIM and how cryptographic signatures authenticate your emails.
What is DKIM?¶
DKIM (DomainKeys Identified Mail) is an email authentication method that uses cryptographic signatures to verify that an email message:
- Was sent by an authorized mail server for the domain
- Has not been altered in transit
- Can be trusted by the recipient
DKIM adds a digital signature to the email header, which receiving servers can validate using a public key published in DNS.
The Problem DKIM Solves¶
Email was designed without built-in security. Attackers can:
- Modify email content during transit
- Spoof sender addresses
- Inject malicious content into legitimate emails
DKIM ensures email integrity and authenticity through cryptography.
How DKIM Works¶
sequenceDiagram
participant Sender as Sending Server
participant DNS as DNS Server
participant Receiver as Receiving Server
Note over Sender: 1. Create email
Sender->>Sender: Generate signature<br/>using private key
Sender->>Receiver: 2. Send email with<br/>DKIM-Signature header
Receiver->>DNS: 3. Query DKIM public key<br/>selector._domainkey.example.com
DNS->>Receiver: 4. Return public key<br/>v=DKIM1; k=rsa; p=MIGfMA...
Receiver->>Receiver: 5. Verify signature<br/>using public key
Note over Receiver: ✓ Signature Valid<br/>✓ Email Unmodified
The DKIM Process¶
- Signing - Sending server creates a hash of email content and headers
- Private Key - Hash is encrypted with the domain's private key
- DKIM Header - Signature is added to email as
DKIM-Signatureheader - Transmission - Email is sent with signature
- Public Key Lookup - Receiving server fetches public key from DNS
- Verification - Receiver decrypts signature and validates hash
DKIM Signature Anatomy¶
DKIM-Signature Header¶
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=google;
h=from:to:subject:date:message-id;
bh=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=;
b=WfEqpJVHLgFq9fEz3...
Key Components¶
| Tag | Name | Description |
|---|---|---|
v= |
Version | DKIM version (always 1) |
a= |
Algorithm | Signing algorithm (e.g., rsa-sha256) |
c= |
Canonicalization | How headers/body are normalized |
d= |
Domain | Signing domain |
s= |
Selector | Identifies which public key to use |
h= |
Headers | Which headers are signed |
bh= |
Body Hash | Hash of email body |
b= |
Signature | Encrypted signature data |
t= |
Timestamp | Signature creation time (optional) |
x= |
Expiration | Signature expiration (optional) |
DKIM Selectors¶
A selector is a string that identifies a specific DKIM public key. It allows domains to have multiple keys simultaneously.
Why Use Selectors?¶
- Key Rotation - Smoothly transition to new keys
- Multiple Servers - Different keys for different mail servers
- Service Separation - Different keys for different services (transactional vs marketing)
Selector Format¶
Examples:
google._domainkey.example.com
default._domainkey.example.com
mail._domainkey.example.com
2024-q1._domainkey.example.com
Common Selector Names¶
| Provider | Typical Selector |
|---|---|
| Google Workspace | google |
| Microsoft 365 | selector1, selector2 |
| Mailgun | mailo, mta |
| SendGrid | s1, s2 |
| Amazon SES | amazonses |
| Custom | default, mail, key1 |
DKIM DNS Record¶
TXT Record Format¶
DKIM Record Tags¶
| Tag | Required | Description | Example |
|---|---|---|---|
v= |
✅ Yes | Version (always DKIM1) |
v=DKIM1 |
k= |
❌ No | Key type | k=rsa (default)k=ed25519 |
p= |
✅ Yes | Public key (base64) | p=MIGfMA0GCSqGSIb3... |
t= |
❌ No | Flags | t=s (strict mode)t=y (testing) |
h= |
❌ No | Acceptable hash algorithms | h=sha256 |
s= |
❌ No | Service type | s=email or s=* |
n= |
❌ No | Notes | n=Test key |
Example Records¶
Minimal DKIM Record:
Full DKIM Record:
google._domainkey.example.com. IN TXT (
"v=DKIM1; k=rsa; t=s; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0v2Ds..."
)
Revoked DKIM Key:
Empty p= tag indicates the key has been revoked.
DKIM Algorithms¶
RSA (Most Common)¶
RSA-SHA256 is the standard algorithm:
Key Sizes: - 1024-bit - ⚠️ Deprecated (weak, but still common) - 2048-bit - ✅ Recommended minimum - 4096-bit - ✅ Maximum security (larger DNS records)
Ed25519 (Modern)¶
Ed25519 is a modern elliptic curve algorithm:
Benefits: - Smaller keys (32 bytes vs 256-512 bytes for RSA) - Faster signing and verification - More secure than RSA-2048
Drawbacks: - Not universally supported yet - Fewer tools available
Generating DKIM Keys¶
Using OpenSSL (RSA)¶
Generate 2048-bit RSA key pair:
# Generate private key
openssl genrsa -out dkim_private.pem 2048
# Extract public key
openssl rsa -in dkim_private.pem -pubout -outform PEM -out dkim_public.pem
# Convert public key to DNS format
openssl rsa -in dkim_private.pem -pubout -outform DER | \
openssl base64 -A
Result:
DNS Record:
default._domainkey.example.com. IN TXT (
"v=DKIM1; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0v2DsZhGg9oF..."
)
Using OpenSSL (Ed25519)¶
# Generate Ed25519 key pair
openssl genpkey -algorithm ed25519 -out dkim_ed25519_private.pem
# Extract public key
openssl pkey -in dkim_ed25519_private.pem -pubout -outform DER | \
tail -c 32 | \
openssl base64 -A
Using opendkim-genkey¶
# Install opendkim tools
sudo apt-get install opendkim-tools # Ubuntu/Debian
brew install opendkim # macOS
# Generate key
opendkim-genkey -d example.com -s default -b 2048
# Creates two files:
# default.private (private key for mail server)
# default.txt (public key for DNS)
Output (default.txt):
default._domainkey.example.com. IN TXT (
"v=DKIM1; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
) ;
Canonicalization¶
Canonicalization defines how email headers and body are normalized before signing/verification.
Why Canonicalization Matters¶
Email can be modified during transit: - Whitespace added/removed - Headers reformatted - Line endings changed (CRLF vs LF)
Canonicalization ensures these benign changes don't break signatures.
Canonicalization Modes¶
Simple Canonicalization¶
No modifications - Strict byte-for-byte comparison
- Header: No changes allowed
- Body: Only trailing empty lines removed
Use case: Maximum security, but fragile
Relaxed Canonicalization¶
Normalize whitespace - More forgiving
- Header: Collapse whitespace, lowercase header names
- Body: Collapse whitespace, remove trailing whitespace
Use case: Recommended - balances security and compatibility
Canonicalization Syntax¶
Examples:
c=simple/simple (strict)
c=relaxed/relaxed (recommended)
c=relaxed/simple (mixed)
c=simple (simple for both)
Signed Headers¶
The h= tag specifies which headers are included in the signature.
Recommended Headers to Sign¶
Essential headers:
- from - ✅ Always sign (required for DMARC)
- to - ✅ Recommended
- subject - ✅ Recommended
- date - ✅ Recommended
- message-id - ✅ Recommended
Optional but useful:
- references - For threaded conversations
- in-reply-to - For replies
- list-id - For mailing lists
- content-type - Protect content structure
Headers to Avoid Signing¶
Don't sign headers that change in transit:
- received - Added by each mail server
- return-path - May be rewritten
- delivered-to - Added by final server
- x-forwarded-* - May change during forwarding
DKIM Alignment (for DMARC)¶
For DMARC to pass, DKIM must be aligned with the From header domain.
Relaxed Alignment (Default)¶
The organizational domain must match:
Strict Alignment¶
The exact domain must match:
DMARC Setting:
Setting Up DKIM¶
Step 1: Generate Key Pair¶
Step 2: Configure Mail Server¶
Postfix with OpenDKIM:
# /etc/opendkim.conf
Domain example.com
Selector default
KeyFile /etc/opendkim/keys/default.private
Socket inet:8891@localhost
Canonicalization relaxed/relaxed
Google Workspace: 1. Admin Console → Apps → Google Workspace → Gmail 2. Authenticate email → Generate new record 3. Copy selector and TXT record value
Microsoft 365: 1. Microsoft 365 Admin Center → Setup → Domains 2. Select domain → DNS settings 3. Add DKIM records for selector1 and selector2
Step 3: Publish Public Key to DNS¶
default._domainkey.example.com. IN TXT (
"v=DKIM1; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
)
Important: - Set TTL to 300 (5 minutes) initially for testing - Increase to 3600 (1 hour) or higher once stable
Step 4: Test DKIM Signing¶
Send a test email and check headers:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=default;
h=from:to:subject:date;
bh=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=;
b=WfEqpJVHLgFq9fEz3dD...
Step 5: Verify with Tools¶
# Using ReputeAPI
curl "https://api.reputeapi.com/api/v1/check?domain=example.com&selectors=default" \
-H "X-API-Key: your-api-key"
# Using dig
dig default._domainkey.example.com TXT +short
# Using mail-tester
# Send email to check@mail-tester.com and check score
DKIM Key Rotation¶
Regular key rotation is a security best practice.
Why Rotate Keys?¶
- Security - Limit exposure if key is compromised
- Compliance - Some regulations require periodic rotation
- Best Practice - Similar to password rotation
Key Rotation Strategy¶
Option 1: Dual-Selector Method (Zero Downtime)¶
Timeline:
Week 1: Publish new key (selector2), keep signing with old (selector1)
Week 2: Start signing with new key (selector2)
Week 3: Remove old key (selector1)
Week 1 - Publish new key:
selector1._domainkey.example.com. IN TXT "v=DKIM1; p=OLD_KEY..."
selector2._domainkey.example.com. IN TXT "v=DKIM1; p=NEW_KEY..."
Week 2 - Switch signing:
# Update mail server to use selector2
Selector selector2
KeyFile /etc/opendkim/keys/selector2.private
Week 3 - Remove old key:
Option 2: Dated Selectors¶
2024-q1._domainkey.example.com. IN TXT "v=DKIM1; p=KEY1..."
2024-q2._domainkey.example.com. IN TXT "v=DKIM1; p=KEY2..."
2024-q3._domainkey.example.com. IN TXT "v=DKIM1; p=KEY3..."
Rotate quarterly or annually.
Revoke Old Keys¶
When removing a key, explicitly revoke it:
Empty p= prevents replay attacks with old signatures.
Common DKIM Issues¶
Issue 1: DKIM Not Found¶
Problem: No DKIM record found for common selectors
Symptoms:
- ReputeAPI reports DKIM_MISSING
- Emails fail DKIM validation
Fixes:
-
Check DNS record exists:
-
Verify selector name: Check DKIM-Signature header in sent email:
Then checkgoogle._domainkey.example.com -
Publish correct selector:
Issue 2: DKIM Key Too Short¶
Problem: Key length less than 2048 bits
Symptoms:
- ReputeAPI reports DKIM_KEY_TOO_SHORT
- Security warnings
Fix: Generate new 2048-bit or 4096-bit key:
Issue 3: DKIM Signature Invalid¶
Problem: Signature verification fails
Possible causes:
- Email modified in transit
-
Check canonicalization: use
relaxed/relaxed -
Clock skew
- Signature timestamp (
t=) too far in past/future -
Solution: Sync server clocks with NTP
-
Wrong private key
- Public key in DNS doesn't match private key
-
Regenerate and republish
-
DNS record issues
- TXT record truncated (>255 characters)
- Solution: Split into multiple strings
Issue 4: DNS TXT Record Too Long¶
Problem: DKIM public key exceeds 255 characters
Solution: Split into multiple strings:
# Instead of:
default._domainkey.example.com. IN TXT "v=DKIM1; p=VERY_LONG_KEY..."
# Use:
default._domainkey.example.com. IN TXT (
"v=DKIM1; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
"0v2DsZhGg9oFYKd5..."
)
Issue 5: Multiple DKIM Signatures¶
Problem: Email has multiple DKIM signatures, some fail
This is OK! Only ONE signature needs to pass for DKIM to pass. Multiple signatures are common: - Mailing lists add their own signature - Email forwarders may re-sign - ESPs add signatures
Example:
DKIM-Signature: v=1; d=example.com; s=default; ... (PASS)
DKIM-Signature: v=1; d=mailchimp.com; s=k1; ... (FAIL)
Result: DKIM Pass (at least one valid signature)
DKIM for Different Scenarios¶
Scenario 1: Single Mail Server¶
Scenario 2: Multiple Mail Servers¶
# Different keys per server
server1._domainkey.example.com. IN TXT "v=DKIM1; p=KEY1..."
server2._domainkey.example.com. IN TXT "v=DKIM1; p=KEY2..."
Scenario 3: Different Keys by Service¶
# Transactional email
transactional._domainkey.example.com. IN TXT "v=DKIM1; p=KEY1..."
# Marketing email
marketing._domainkey.example.com. IN TXT "v=DKIM1; p=KEY2..."
# Internal email
internal._domainkey.example.com. IN TXT "v=DKIM1; p=KEY3..."
Scenario 4: Using Third-Party ESPs¶
Mailgun:
SendGrid:
s1._domainkey.example.com. IN TXT "k=rsa; t=s; p=MIGfMA0..."
s2._domainkey.example.com. IN TXT "k=rsa; t=s; p=MIIBIjAN..."
Amazon SES:
Testing DKIM¶
Using ReputeAPI¶
curl -X GET \
"https://api.reputeapi.com/api/v1/check?domain=example.com&selectors=default,google" \
-H "X-API-Key: your-api-key"
Response:
{
"dkim": {
"discovered_selectors": ["default", "google"],
"validated_keys": [
{
"selector": "default",
"valid": true,
"key_size": 2048,
"algorithm": "rsa-sha256",
"record": "v=DKIM1; k=rsa; p=MIIBIj..."
}
]
},
"issues": [
{
"code": "DKIM_KEY_TOO_SHORT",
"severity": "high",
"message": "DKIM key length below 2048 bits",
"remediation": "Generate a new 2048-bit or 4096-bit RSA key"
}
]
}
Using Command Line¶
Query DKIM DNS record:
Send test email:
# Send to Gmail and check headers
echo "Test" | mail -s "DKIM Test" your-gmail@gmail.com
# Check headers in Gmail:
# 1. Open email
# 2. Click "..." → Show original
# 3. Look for "DKIM: PASS"
Online Tools¶
DKIM Best Practices¶
1. Use 2048-bit Keys Minimum¶
# Generate 2048-bit key
opendkim-genkey -d example.com -s default -b 2048
# Or 4096-bit for extra security
opendkim-genkey -d example.com -s default -b 4096
2. Use Relaxed Canonicalization¶
More compatible with email forwarding and mailing lists.
3. Sign Essential Headers¶
Always include from (required for DMARC alignment).
4. Rotate Keys Regularly¶
Rotate keys annually or quarterly:
5. Use Multiple Selectors for Key Rotation¶
selector1._domainkey.example.com. IN TXT "v=DKIM1; p=CURRENT_KEY"
selector2._domainkey.example.com. IN TXT "v=DKIM1; p=NEW_KEY"
6. Monitor DKIM Failures¶
Use DMARC reports to monitor DKIM pass rates:
7. Secure Private Keys¶
# Restrict permissions
chmod 600 /etc/opendkim/keys/*.private
chown opendkim:opendkim /etc/opendkim/keys/*.private
# Never commit to version control
echo "*.private" >> .gitignore
DKIM and Email Forwarding¶
Problem: Email forwarding can break DKIM signatures.
Why Forwarding Breaks DKIM¶
- Subject line modified (
[Fwd: ...]) - Body modified (footers added)
- Headers added (
Forwarded-By:)
Result: DKIM signature becomes invalid.
Solutions¶
Option 1: ARC (Authenticated Received Chain)¶
ARC preserves authentication results across forwarding:
Supported by Gmail, Outlook, Yahoo.
Option 2: SRS (Sender Rewriting Scheme)¶
Rewrites envelope sender to forwarding domain:
Option 3: Use Relaxed Canonicalization¶
Tolerates minor modifications.
DKIM Validation with ReputeAPI¶
Check DKIM Configuration¶
import requests
response = requests.get(
"https://api.reputeapi.com/api/v1/check",
params={
"domain": "example.com",
"selectors": "default,google,mail"
},
headers={"X-API-Key": "your-api-key"}
)
result = response.json()
# Check DKIM status
if not result['dkim']['discovered_selectors']:
print("❌ No DKIM keys found")
else:
print(f"✅ Found selectors: {result['dkim']['discovered_selectors']}")
for key in result['dkim']['validated_keys']:
if key['valid']:
print(f" ✓ {key['selector']}: {key['key_size']}-bit {key['algorithm']}")
else:
print(f" ✗ {key['selector']}: Invalid")
# Check for DKIM issues
dkim_issues = [i for i in result['issues'] if i['category'] == 'dkim']
for issue in dkim_issues:
print(f"\n[{issue['severity']}] {issue['message']}")
print(f"Fix: {issue['remediation']}")
Monitor DKIM Keys¶
def monitor_dkim_keys(domain, expected_selectors):
"""Monitor DKIM key status"""
response = requests.get(
"https://api.reputeapi.com/api/v1/check",
params={
"domain": domain,
"selectors": ",".join(expected_selectors)
},
headers={"X-API-Key": "your-api-key"}
)
result = response.json()
found = set(result['dkim']['discovered_selectors'])
expected = set(expected_selectors)
# Check for missing keys
missing = expected - found
if missing:
send_alert(f"Missing DKIM keys: {missing}")
# Check for weak keys
for key in result['dkim']['validated_keys']:
if key['key_size'] < 2048:
send_alert(f"Weak DKIM key: {key['selector']} ({key['key_size']}-bit)")
return result
Common Questions¶
Do I need DKIM if I have SPF?¶
Yes! DKIM and SPF serve different purposes: - SPF validates the sending server - DKIM validates message integrity and authenticity
Both are recommended for full protection.
Can I have multiple DKIM keys?¶
Yes! Multiple keys are common and recommended: - Key rotation (old and new keys overlap) - Different services (transactional vs marketing) - Multiple mail servers
How often should I rotate DKIM keys?¶
Recommendation: - Annually for most organizations - Quarterly for high-security environments - Immediately if key is compromised
What key size should I use?¶
Recommendation: - Minimum: 2048-bit RSA - Recommended: 2048-bit RSA or Ed25519 - Maximum security: 4096-bit RSA
Avoid 1024-bit (too weak) and >4096-bit (DNS issues).
Does DKIM encrypt email?¶
No. DKIM only signs emails to prove authenticity. For encryption, use: - TLS/STARTTLS for transport encryption - S/MIME or PGP for end-to-end encryption
Why do emails from mailing lists fail DKIM?¶
Mailing lists often modify emails (add footers, change subject), breaking original signatures. Some mailing lists: - Remove original DKIM signatures - Add their own signature - Use ARC to preserve authentication
Related Concepts¶
- SPF Explained - Sender IP validation
- DMARC Explained - Policy enforcement
- Email Authentication - How they work together
- DNS Configuration - Setting up DKIM records
API Resources¶
- GET /api/v1/check - Comprehensive DKIM validation
- POST /api/v1/recommendations - Get DKIM fix recommendations
- Mailflow Security Score - How DKIM affects your score