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:

  1. Was sent by an authorized mail server for the domain
  2. Has not been altered in transit
  3. 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

  1. Signing - Sending server creates a hash of email content and headers
  2. Private Key - Hash is encrypted with the domain's private key
  3. DKIM Header - Signature is added to email as DKIM-Signature header
  4. Transmission - Email is sent with signature
  5. Public Key Lookup - Receiving server fetches public key from DNS
  6. 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

<selector>._domainkey.<domain>

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

selector._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCS..."

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:

default._domainkey.example.com. IN TXT "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."

Full DKIM Record:

google._domainkey.example.com. IN TXT (
  "v=DKIM1; k=rsa; t=s; "
  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0v2Ds..."
)

Revoked DKIM Key:

old._domainkey.example.com. IN TXT "v=DKIM1; p="

Empty p= tag indicates the key has been revoked.


DKIM Algorithms

RSA (Most Common)

RSA-SHA256 is the standard algorithm:

a=rsa-sha256
k=rsa

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:

a=ed25519-sha256
k=ed25519

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:

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0v2DsZhGg9oF...

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

c=simple/simple
  • Header: No changes allowed
  • Body: Only trailing empty lines removed

Use case: Maximum security, but fragile

Relaxed Canonicalization

Normalize whitespace - More forgiving

c=relaxed/relaxed
  • Header: Collapse whitespace, lowercase header names
  • Body: Collapse whitespace, remove trailing whitespace

Use case: Recommended - balances security and compatibility

Canonicalization Syntax

c=<header-canon>/<body-canon>

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.

h=from:to:subject:date:message-id:references:in-reply-to

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:

DKIM d=: mail.example.com
From:    user@example.com
✓ DKIM Aligned (example.com matches)

Strict Alignment

The exact domain must match:

DKIM d=: mail.example.com
From:    user@example.com
✗ NOT aligned (mail.example.com ≠ example.com)

DMARC Setting:

# Relaxed (default)
v=DMARC1; p=reject; adkim=r

# Strict
v=DMARC1; p=reject; adkim=s


Setting Up DKIM

Step 1: Generate Key Pair

opendkim-genkey -d example.com -s default -b 2048

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:

selector2._domainkey.example.com. IN TXT "v=DKIM1; p=NEW_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:

old._domainkey.example.com. IN TXT "v=DKIM1; p="

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:

  1. Check DNS record exists:

    dig default._domainkey.example.com TXT +short
    

  2. Verify selector name: Check DKIM-Signature header in sent email:

    s=google
    
    Then check google._domainkey.example.com

  3. Publish correct selector:

    google._domainkey.example.com. IN TXT "v=DKIM1; p=YOUR_KEY"
    

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:

opendkim-genkey -d example.com -s default -b 2048

Issue 3: DKIM Signature Invalid

Problem: Signature verification fails

Possible causes:

  1. Email modified in transit
  2. Check canonicalization: use relaxed/relaxed

  3. Clock skew

  4. Signature timestamp (t=) too far in past/future
  5. Solution: Sync server clocks with NTP

  6. Wrong private key

  7. Public key in DNS doesn't match private key
  8. Regenerate and republish

  9. DNS record issues

  10. TXT record truncated (>255 characters)
  11. 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

# Single DKIM key
default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=KEY..."
# Mail server config
Domain      example.com
Selector    default
KeyFile     /etc/opendkim/keys/default.private

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:

mailo._domainkey.example.com. IN TXT "k=rsa; p=MIGfMA0GCS..."

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:

amazonses._domainkey.example.com. IN CNAME "amazonses.example.com.dkim.amazonses.com"


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:

dig default._domainkey.example.com TXT +short

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

Canonicalization    relaxed/relaxed

More compatible with email forwarding and mailing lists.

3. Sign Essential Headers

SignHeaders    from,to,subject,date,message-id

Always include from (required for DMARC alignment).

4. Rotate Keys Regularly

Rotate keys annually or quarterly:

2024-q1._domainkey.example.com
2024-q2._domainkey.example.com
2024-q3._domainkey.example.com

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:

_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc@example.com"

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

  1. Subject line modified ([Fwd: ...])
  2. Body modified (footers added)
  3. Headers added (Forwarded-By:)

Result: DKIM signature becomes invalid.

Solutions

Option 1: ARC (Authenticated Received Chain)

ARC preserves authentication results across forwarding:

ARC-Authentication-Results: i=1; mx.google.com;
  dkim=pass header.d=example.com

Supported by Gmail, Outlook, Yahoo.

Option 2: SRS (Sender Rewriting Scheme)

Rewrites envelope sender to forwarding domain:

Original:  sender@example.com
Forwarded: SRS0=hash=domain.com@forwarder.com

Option 3: Use Relaxed Canonicalization

Canonicalization    relaxed/relaxed

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



API Resources