RFC 6376 - DKIM-Signaturen
Grundinformationen
- RFC-Nummer: 6376
- Titel: DomainKeys Identified Mail (DKIM) Signatures
- Deutscher Titel: Domainschlüssel-identifizierte Mail-Signaturen
- Veröffentlichung: September 2011
- Status: VORGESCHLAGENER STANDARD (PROPOSED STANDARD)
- Autoren: D. Crocker, T. Hansen, M. Kucherawy
Zusammenfassung (Abstract)
DomainKeys Identified Mail (DKIM) ist eine E-Mail-Authentifizierungsmethode auf Domain-Ebene. Sie ermöglicht es Organisationen, mithilfe kryptografischer Signaturen Verantwortung für E-Mail-Nachrichten zu übernehmen. Empfänger können die Signatur überprüfen, um zu bestätigen, dass die E-Mail tatsächlich von der behaupteten Domain stammt und ihr Inhalt nicht manipuliert wurde.
DKIM-Überblick
Was ist DKIM?
Definition:
DKIM = DomainKeys Identified Mail
Funktion: E-Mail-Digitalsignatur
Zweck:
✓ Sender-Domain verifizieren
✓ E-Mail-Manipulation erkennen
✓ Phishing und Fälschung verhindern
Kernkonzept:
Sender signiert mit privatem Schlüssel → Empfänger verifiziert mit öffentlichem Schlüssel
E-Mail-Sicherheitsprobleme:
SMTP-Protokoll-Probleme:
❌ Kann Absenderadresse fälschen
❌ Kann E-Mail-Authentizität nicht verifizieren
❌ Leicht für Phishing ausnutzbar
Phishing-Beispiel:
From: [email protected] (gefälscht)
Tatsächlich: Server des Angreifers
DKIM-Lösung:
✓ Domain-Inhaber signiert E-Mail
✓ Öffentlichen Schlüssel in DNS veröffentlichen
✓ Empfänger verifiziert Signatur
✓ Signatur fehlgeschlagen → ablehnen oder markieren
DKIM-Workflow
Sendeseite (example.com):
1. Mailserver generiert Signatur
- Signiert E-Mail-Header und -Body
- Verwendet privaten Schlüssel (geheim)
2. Signatur zum E-Mail-Header hinzufügen
DKIM-Signature: v=1; a=rsa-sha256; d=example.com; ...
3. E-Mail normal versenden
Empfangsseite:
1. DKIM-Signature-Header extrahieren
2. Öffentlichen Schlüssel von DNS abrufen
Abfrage: default._domainkey.example.com
3. Signatur verifizieren
- E-Mail-Hash neu berechnen
- Signatur mit öffentlichem Schlüssel entschlüsseln
- Hash-Werte vergleichen
4. Ergebnis:
✓ Gültige Signatur → Von example.com, nicht manipuliert
✗ Ungültige Signatur → Gefälscht oder modifiziert
DKIM-Signature-Header Details
Grundformat
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-Beschreibungen
| Tag | Name | Beschreibung | Beispiel | Erforderlich |
|---|---|---|---|---|
v | Version | Versionsnummer | v=1 | Ja |
a | Algorithm (Algorithmus) | Signaturalgorithmus | a=rsa-sha256 | Ja |
b | Signature (Signatur) | Tatsächliche Signatur (Base64) | b=dzdVy... | Ja |
bh | Body Hash (Body-Hash) | E-Mail-Body-Hash | bh=2jUSO... | Ja |
c | Canonicalization (Kanonisierung) | Kanonisierungsalgorithmus | c=relaxed/relaxed | Nein |
d | Domain | Signatur-Domain | d=example.com | Ja |
h | Headers | Signierte Header-Felder | h=From:To:Subject | Ja |
i | Identity (Identität) | Unterzeichner-Identität | [email protected] | Nein |
l | Body Length (Body-Länge) | Signierte Body-Länge | l=1000 | Nein |
q | Query Method (Abfragemethode) | Öffentlicher Schlüssel Abfragemethode | q=dns/txt | Nein |
s | Selector (Selektor) | Selektor | s=default | Ja |
t | Timestamp (Zeitstempel) | Signatur-Zeitstempel | t=1234567890 | Nein |
x | Expire Time (Ablaufzeit) | Ablaufzeit | x=1234657890 | Nein |
z | Copied Headers (Kopierte Header) | Originale Header-Kopie | z=From:... | Nein |
Details zu Schlüsselparametern:
a (Algorithm - Algorithmus):
Unterstützte Algorithmen:
rsa-sha256 (empfohlen)
rsa-sha1 (veraltet, unsicher)
Beispiel:
a=rsa-sha256
c (Canonicalization - Kanonisierung):
Format: c=<header>/<body>
Algorithmen:
- simple: Strenge Übereinstimmung
- relaxed: Lockere Übereinstimmung (ignoriert Leerzeichen, Groß-/Kleinschreibung usw.)
Beispiele:
c=relaxed/relaxed (empfohlen, toleriert Mailserver-Änderungen)
c=simple/simple (streng, jede Änderung führt zum Verifizierungsfehler)
d (Domain):
Signatur-Domain (muss Sender-Domain oder Eltern-Domain sein)
Gültig:
From: [email protected]
d=example.com ✓ (Eltern-Domain)
Ungültig:
From: [email protected]
d=other.com ✗ (nicht verwandte Domain)
s (Selector - Selektor):
Selektor: Für DNS-Abfragen verwendet
Zweck:
- Mehrere Schlüssel unterstützen
- Schlüsselrotation
- Verschiedene Dienste verwenden verschiedene Schlüssel
DNS-Abfrage:
s=default, d=example.com
→ Abfrage: default._domainkey.example.com
h (Headers):
Signierte Header-Felder (durch Doppelpunkt getrennt)
Empfohlen zu signieren:
- From (erforderlich!)
- To
- Subject
- Date
- Message-ID
Beispiel:
h=From:To:Subject:Date:Message-ID
Hinweis:
From-Feld muss enthalten sein!
DNS-Öffentlicher-Schlüssel-Eintrag
TXT-Eintrag-Format
Abfrage: <selector>._domainkey.<domain>
Beispiel: default._domainkey.example.com
TXT-Eintrag-Inhalt:
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...
Tag-Beschreibung:
v=DKIM1 Version
k=rsa Schlüsseltyp
p=<öffentlicher Schlüssel> Base64-kodierter öffentlicher Schlüssel
Öffentlicher-Schlüssel-Tag-Details
| Tag | Beschreibung | Beispiel | Erforderlich |
|---|---|---|---|
v | Version | v=DKIM1 | Nein |
k | Schlüsseltyp | k=rsa | Nein |
p | Öffentlicher Schlüssel (Base64) | p=MIGfMA0... | Ja |
h | Akzeptable Hash-Algorithmen | h=sha256 | Nein |
s | Diensttyp | s=email | Nein |
t | Flags | t=y (Testmodus) | Nein |
n | Notizen | n=Notes | Nein |
Vollständiges Beispiel:
default._domainkey.example.com. IN TXT (
"v=DKIM1; k=rsa; "
"p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3QEKyU1fSma0axspqYK5iAj+54lsAg4qRRCnpKK68hawSIhv5TGT4j"
"..."
)
Hinweise:
- Kann in mehrere Strings aufgeteilt werden (DNS-Limit 255 Bytes)
- Leerzeichen werden ignoriert
- Leeres p-Tag → Schlüssel widerrufen
Implementierungsbeispiele
Schlüsselpaar generieren
# 2048-Bit RSA-Privatschlüssel generieren
openssl genrsa -out dkim_private.pem 2048
# Öffentlichen Schlüssel extrahieren
openssl rsa -in dkim_private.pem -pubout -out dkim_public.pem
# In DNS-Format konvertieren (Header und Zeilenumbrüche entfernen)
cat dkim_public.pem | grep -v "BEGIN\|END" | tr -d '\n'
Ausgabe:
Privatschlüssel (dkim_private.pem):
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
-----END RSA PRIVATE KEY-----
Öffentlicher Schlüssel (dkim_public.pem):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
DNS-Format:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
Node.js-Signatur-Implementierung
const crypto = require('crypto');
const dns = require('dns').promises;
class DKIMSigner {
constructor(domain, selector, privateKey) {
this.domain = domain;
this.selector = selector;
this.privateKey = privateKey;
}
// E-Mail signieren
signEmail(headers, body) {
// 1. Body kanonisieren
const canonicalBody = this.canonicalizeBody(body);
// 2. Body-Hash berechnen
const bodyHash = crypto
.createHash('sha256')
.update(canonicalBody)
.digest('base64');
// 3. DKIM-Header erstellen (ohne 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=' // Leere Signatur, später zu füllen
].join('; ');
// 4. Header kanonisieren
const canonicalHeaders = this.canonicalizeHeaders(headers, dkimHeader);
// 5. Signatur generieren
const sign = crypto.createSign('RSA-SHA256');
sign.update(canonicalHeaders);
const signature = sign.sign(this.privateKey, 'base64');
// 6. Vollständige DKIM-Signature
return dkimHeader.replace('b=', 'b=' + signature);
}
// Body kanonisieren (relaxed)
canonicalizeBody(body) {
return body
.replace(/[ \t]+(\r\n)/g, '$1') // Nachfolgende Leerzeichen entfernen
.replace(/(\r\n)+$/, '\r\n'); // Nur einen abschließenden Zeilenumbruch behalten
}
// Header kanonisieren (relaxed)
canonicalizeHeaders(headers, dkimHeader) {
const headersToSign = ['from', 'to', 'subject', 'date'];
let canonical = '';
// Zu signierende Header hinzufügen
for (const name of headersToSign) {
const value = headers[name];
if (value) {
canonical += name + ':' + value.trim() + '\r\n';
}
}
// DKIM-Signature-Header hinzufügen (ohne b-Tag-Wert)
canonical += 'dkim-signature:' +
dkimHeader.split('b=')[0] + 'b=';
return canonical;
}
}
// Verwendung
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);
Verifizierungs-Implementierung
class DKIMVerifier {
async verifyEmail(dkimSignature, headers, body) {
// 1. DKIM-Signature analysieren
const tags = this.parseDKIMSignature(dkimSignature);
// 2. Öffentlichen Schlüssel abrufen
const publicKey = await this.getPublicKey(tags.d, tags.s);
// 3. Body-Hash verifizieren
const bodyValid = this.verifyBodyHash(body, tags.bh, tags.c);
if (!bodyValid) {
return { valid: false, reason: 'Body hash mismatch' };
}
// 4. Signatur verifizieren
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);
// TXT-Eintrag analysieren
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) {
// Signaturwert extrahieren
const signature = tags.b;
// Signaturdaten rekonstruieren
const canonicalHeaders = this.canonicalizeHeaders(
headers,
dkimSignature.split('b=')[0] + 'b='
);
// Verifizieren
const verify = crypto.createVerify('RSA-SHA256');
verify.update(canonicalHeaders);
return verify.verify(publicKey, signature, 'base64');
}
}
// Verwendung
const verifier = new DKIMVerifier();
const result = await verifier.verifyEmail(dkimSignature, headers, body);
if (result.valid) {
console.log(`✓ Gültige DKIM-Signatur von ${result.domain}`);
} else {
console.log(`✗ Ungültige DKIM-Signatur: ${result.reason}`);
}
Best Practices
1. Schlüsselverwaltung
Schlüssellänge:
✓ Mindestens 2048 Bit
✓ Empfohlen 2048 oder 3072 Bit
✗ 1024 Bit vermeiden (unsicher)
Schlüsselrotation:
- Schlüssel alle 1-2 Jahre wechseln
- Neue und alte Schlüssel gleichzeitig veröffentlichen (verschiedene Selektoren verwenden)
- Alte Schlüssel nach Übergangszeit entfernen
Selektor-Benennung:
✓ Nach Datum: 2024-01
✓ Nach Zweck: mailserver1
✓ Nach Rotation: key1, key2
2. Header-Auswahl
// Empfohlene zu signierende Header
const recommendedHeaders = [
'From', // Erforderlich!
'To',
'Subject',
'Date',
'Message-ID',
'Reply-To',
'Cc',
'MIME-Version',
'Content-Type'
];
// Volatile Header vermeiden
const avoidHeaders = [
'Received', // Von jedem Relay hinzugefügt
'Return-Path', // Kann sich während des Transports ändern
'X-*' // Benutzerdefinierte Header können geändert werden
];
3. Testmodus
t=y-Flag zum DNS-Eintrag hinzufügen:
v=DKIM1; k=rsa; t=y; p=MIGfMA...
Zweck:
- Zeigt Testmodus an
- Verifizierungsfehler lehnt E-Mail nicht ab
- Für initiale Bereitstellungstests verwendet
Referenzen
DKIM-bezogene RFCs:
- [RFC 6376] DKIM Signatures ← Dieses Dokument
- [RFC 8301] Cryptographic Algorithm and Key Usage Update to DKIM
- [RFC 8463] A New Cryptographic Signature Method For DKIM
Verwandte Standards:
- [RFC 7489] DMARC (Basiert auf DKIM und SPF)
- [RFC 7208] SPF (Sender Policy Framework)
Zusammenfassung: DKIM bietet Domain-Level-Authentifizierung für E-Mail durch digitale Signaturtechnologie und bildet die Grundlage der modernen E-Mail-Sicherheit. Eine ordnungsgemäße Implementierung von DKIM kann die E-Mail-Zustellbarkeit erheblich verbessern, Fehlalarme als Spam reduzieren und die Grundlage für fortschrittlichere E-Mail-Sicherheitsmechanismen wie DMARC schaffen.