Skip to main content

RFC 6376 - DKIM Signatures

Basic Information

  • RFC Number: 6376
  • Title: DomainKeys Identified Mail (DKIM) Signatures
  • Published: September 2011
  • Status: PROPOSED STANDARD
  • Authors: D. Crocker, T. Hansen, M. Kucherawy

Abstract

DomainKeys Identified Mail (DKIM) is a domain-level email authentication method. It allows organizations to take responsibility for email messages using cryptographic signatures. Recipients can verify the signature to confirm that the email indeed comes from the claimed domain and that its content has not been tampered with.

DKIM Overview

What is DKIM?

Definition:

DKIM = DomainKeys Identified Mail
Function: Email Digital Signature
Purpose:
✓ Verify sender domain
✓ Detect email tampering
✓ Prevent phishing and forgery

Core Concept:
Sender signs with private key → Recipient verifies with public key

Email Security Issues:

SMTP Protocol Problems:
❌ Can forge sender address
❌ Cannot verify email authenticity
❌ Easily exploited for phishing

Phishing Example:
From: [email protected] (forged)
Actual: Attacker's server

DKIM Solution:
✓ Domain owner signs email
✓ Publish public key in DNS
✓ Recipient verifies signature
✓ Signature fails → reject or mark

DKIM Workflow

Sending Side (example.com):
1. Mail server generates signature
- Signs email headers and body
- Uses private key (secret)

2. Add signature to email header
DKIM-Signature: v=1; a=rsa-sha256; d=example.com; ...

3. Send email normally

Receiving Side:
1. Extract DKIM-Signature header

2. Retrieve public key from DNS
Query: default._domainkey.example.com

3. Verify signature
- Recalculate email hash
- Decrypt signature with public key
- Compare hash values

4. Result:
✓ Valid signature → From example.com, not tampered
✗ Invalid signature → Forged or modified

DKIM-Signature Header Details

Basic Format

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=default; t=1234567890;
bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
h=From:To:Subject:Date;
b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD08b9/0i
+=hB...

Tag Descriptions

TagNameDescriptionExampleRequired
vVersionVersion numberv=1Yes
aAlgorithmSignature algorithma=rsa-sha256Yes
bSignatureActual signature (Base64)b=dzdVy...Yes
bhBody HashEmail body hashbh=2jUSO...Yes
cCanonicalizationCanonicalization algorithmc=relaxed/relaxedNo
dDomainSigning domaind=example.comYes
hHeadersSigned header fieldsh=From:To:SubjectYes
iIdentitySigner identity[email protected]No
lBody LengthSigned body lengthl=1000No
qQuery MethodPublic key query methodq=dns/txtNo
sSelectorSelectors=defaultYes
tTimestampSignature timestampt=1234567890No
xExpire TimeExpiration timex=1234657890No
zCopied HeadersOriginal headers copyz=From:...No

Key Parameter Details:

a (Algorithm):

Supported algorithms:
rsa-sha256 (recommended)
rsa-sha1 (deprecated, insecure)

Example:
a=rsa-sha256

c (Canonicalization):

Format: c=<header>/<body>

Algorithms:
- simple: Strict matching
- relaxed: Relaxed matching (ignores whitespace, case, etc.)

Examples:
c=relaxed/relaxed (recommended, tolerates mail server modifications)
c=simple/simple (strict, any modification causes verification failure)

d (Domain):

Signing domain (must be sender domain or parent domain)

Valid:
From: [email protected]
d=example.com ✓ (parent domain)

Invalid:
From: [email protected]
d=other.com ✗ (unrelated domain)

s (Selector):

Selector: Used for DNS queries

Purpose:
- Support multiple keys
- Key rotation
- Different services use different keys

DNS Query:
s=default, d=example.com
→ Query: default._domainkey.example.com

h (Headers):

Signed header fields (colon-separated)

Recommended to sign:
- From (required!)
- To
- Subject
- Date
- Message-ID

Example:
h=From:To:Subject:Date:Message-ID

Note:
From field must be included!

DNS Public Key Record

TXT Record Format

Query: <selector>._domainkey.<domain>
Example: default._domainkey.example.com

TXT Record Content:
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...

Tag Description:
v=DKIM1 Version
k=rsa Key type
p=<public key> Base64-encoded public key

Public Key Tag Details

TagDescriptionExampleRequired
vVersionv=DKIM1No
kKey typek=rsaNo
pPublic key (Base64)p=MIGfMA0...Yes
hAcceptable hash algorithmsh=sha256No
sService types=emailNo
tFlagst=y (test mode)No
nNotesn=NotesNo

Complete Example:

default._domainkey.example.com. IN TXT (
"v=DKIM1; k=rsa; "
"p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3QEKyU1fSma0axspqYK5iAj+54lsAg4qRRCnpKK68hawSIhv5TGT4j"
"..."
)

Notes:
- Can be split into multiple strings (DNS limit 255 bytes)
- Spaces are ignored
- Empty p tag → key revoked

Implementation Examples

Generate Key Pair

# Generate 2048-bit RSA private key
openssl genrsa -out dkim_private.pem 2048

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

# Convert to DNS format (remove headers and newlines)
cat dkim_public.pem | grep -v "BEGIN\|END" | tr -d '\n'

Output:

Private Key (dkim_private.pem):
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
-----END RSA PRIVATE KEY-----

Public Key (dkim_public.pem):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----

DNS Format:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...

Node.js Signing Implementation

const crypto = require('crypto');
const dns = require('dns').promises;

class DKIMSigner {
constructor(domain, selector, privateKey) {
this.domain = domain;
this.selector = selector;
this.privateKey = privateKey;
}

// Sign email
signEmail(headers, body) {
// 1. Canonicalize body
const canonicalBody = this.canonicalizeBody(body);

// 2. Calculate body hash
const bodyHash = crypto
.createHash('sha256')
.update(canonicalBody)
.digest('base64');

// 3. Build DKIM header (without b tag)
const timestamp = Math.floor(Date.now() / 1000);
const dkimHeader = [
'v=1',
'a=rsa-sha256',
'd=' + this.domain,
's=' + this.selector,
't=' + timestamp,
'c=relaxed/relaxed',
'h=From:To:Subject:Date',
'bh=' + bodyHash,
'b=' // Empty signature, to be filled later
].join('; ');

// 4. Canonicalize headers
const canonicalHeaders = this.canonicalizeHeaders(headers, dkimHeader);

// 5. Generate signature
const sign = crypto.createSign('RSA-SHA256');
sign.update(canonicalHeaders);
const signature = sign.sign(this.privateKey, 'base64');

// 6. Complete DKIM-Signature
return dkimHeader.replace('b=', 'b=' + signature);
}

// Canonicalize body (relaxed)
canonicalizeBody(body) {
return body
.replace(/[ \t]+(\r\n)/g, '$1') // Remove trailing spaces
.replace(/(\r\n)+$/, '\r\n'); // Keep only one trailing newline
}

// Canonicalize headers (relaxed)
canonicalizeHeaders(headers, dkimHeader) {
const headersToSign = ['from', 'to', 'subject', 'date'];
let canonical = '';

// Add headers to sign
for (const name of headersToSign) {
const value = headers[name];
if (value) {
canonical += name + ':' + value.trim() + '\r\n';
}
}

// Add DKIM-Signature header (without b tag value)
canonical += 'dkim-signature:' +
dkimHeader.split('b=')[0] + 'b=';

return canonical;
}
}

// Usage
const privateKey = fs.readFileSync('dkim_private.pem', 'utf8');
const signer = new DKIMSigner('example.com', 'default', privateKey);

const headers = {
from: '[email protected]',
to: '[email protected]',
subject: 'Test Email',
date: new Date().toUTCString()
};

const body = 'This is the email body.\r\n';

const dkimSignature = signer.signEmail(headers, body);
console.log('DKIM-Signature:', dkimSignature);

Verification Implementation

class DKIMVerifier {
async verifyEmail(dkimSignature, headers, body) {
// 1. Parse DKIM-Signature
const tags = this.parseDKIMSignature(dkimSignature);

// 2. Get public key
const publicKey = await this.getPublicKey(tags.d, tags.s);

// 3. Verify body hash
const bodyValid = this.verifyBodyHash(body, tags.bh, tags.c);
if (!bodyValid) {
return { valid: false, reason: 'Body hash mismatch' };
}

// 4. Verify signature
const signatureValid = this.verifySignature(
headers,
dkimSignature,
publicKey,
tags
);

return {
valid: signatureValid,
domain: tags.d,
selector: tags.s
};
}

parseDKIMSignature(signature) {
const tags = {};
signature.split(';').forEach(part => {
const [key, value] = part.trim().split('=');
if (key && value) {
tags[key] = value.trim();
}
});
return tags;
}

async getPublicKey(domain, selector) {
const dnsName = `${selector}._domainkey.${domain}`;
const records = await dns.resolveTxt(dnsName);

// Parse TXT record
const dkimRecord = records[0].join('');
const match = dkimRecord.match(/p=([^;]+)/);

if (!match) {
throw new Error('Public key not found');
}

const publicKeyB64 = match[1];
return `-----BEGIN PUBLIC KEY-----\n${publicKeyB64}\n-----END PUBLIC KEY-----`;
}

verifyBodyHash(body, expectedHash, canonicalization) {
const canonical = this.canonicalizeBody(body);
const hash = crypto
.createHash('sha256')
.update(canonical)
.digest('base64');

return hash === expectedHash;
}

verifySignature(headers, dkimSignature, publicKey, tags) {
// Extract signature value
const signature = tags.b;

// Rebuild signature data
const canonicalHeaders = this.canonicalizeHeaders(
headers,
dkimSignature.split('b=')[0] + 'b='
);

// Verify
const verify = crypto.createVerify('RSA-SHA256');
verify.update(canonicalHeaders);

return verify.verify(publicKey, signature, 'base64');
}
}

// Usage
const verifier = new DKIMVerifier();
const result = await verifier.verifyEmail(dkimSignature, headers, body);

if (result.valid) {
console.log(`✓ Valid DKIM signature from ${result.domain}`);
} else {
console.log(`✗ Invalid DKIM signature: ${result.reason}`);
}

Best Practices

1. Key Management

Key Length:
✓ At least 2048 bits
✓ Recommended 2048 or 3072 bits
✗ Avoid 1024 bits (insecure)

Key Rotation:
- Rotate keys every 1-2 years
- Publish new and old keys simultaneously (use different selectors)
- Remove old keys after transition period

Selector Naming:
✓ By date: 2024-01
✓ By purpose: mailserver1
✓ By rotation: key1, key2

2. Header Selection

// Recommended headers to sign
const recommendedHeaders = [
'From', // Required!
'To',
'Subject',
'Date',
'Message-ID',
'Reply-To',
'Cc',
'MIME-Version',
'Content-Type'
];

// Avoid signing volatile headers
const avoidHeaders = [
'Received', // Added by each relay
'Return-Path', // May change in transit
'X-*' // Custom headers may be modified
];

3. Test Mode

Add t=y flag to DNS record:
v=DKIM1; k=rsa; t=y; p=MIGfMA...

Purpose:
- Indicates test mode
- Verification failure won't reject email
- Used for initial deployment testing

References

DKIM-related RFCs:

  • [RFC 6376] DKIM Signatures ← This document
  • [RFC 8301] Cryptographic Algorithm and Key Usage Update to DKIM
  • [RFC 8463] A New Cryptographic Signature Method For DKIM

Related Standards:

  • [RFC 7489] DMARC (Based on DKIM and SPF)
  • [RFC 7208] SPF (Sender Policy Framework)

Summary: DKIM provides domain-level authentication for email through digital signature technology, forming the foundation of modern email security. Properly implementing DKIM can significantly improve email deliverability, reduce false positives as spam, and lay the groundwork for more advanced email security mechanisms like DMARC.