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
| Tag | Name | Description | Example | Required |
|---|---|---|---|---|
v | Version | Version number | v=1 | Yes |
a | Algorithm | Signature algorithm | a=rsa-sha256 | Yes |
b | Signature | Actual signature (Base64) | b=dzdVy... | Yes |
bh | Body Hash | Email body hash | bh=2jUSO... | Yes |
c | Canonicalization | Canonicalization algorithm | c=relaxed/relaxed | No |
d | Domain | Signing domain | d=example.com | Yes |
h | Headers | Signed header fields | h=From:To:Subject | Yes |
i | Identity | Signer identity | [email protected] | No |
l | Body Length | Signed body length | l=1000 | No |
q | Query Method | Public key query method | q=dns/txt | No |
s | Selector | Selector | s=default | Yes |
t | Timestamp | Signature timestamp | t=1234567890 | No |
x | Expire Time | Expiration time | x=1234657890 | No |
z | Copied Headers | Original headers copy | z=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
| Tag | Description | Example | Required |
|---|---|---|---|
v | Version | v=DKIM1 | No |
k | Key type | k=rsa | No |
p | Public key (Base64) | p=MIGfMA0... | Yes |
h | Acceptable hash algorithms | h=sha256 | No |
s | Service type | s=email | No |
t | Flags | t=y (test mode) | No |
n | Notes | n=Notes | No |
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.