Zum Hauptinhalt springen

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

TagNameBeschreibungBeispielErforderlich
vVersionVersionsnummerv=1Ja
aAlgorithm (Algorithmus)Signaturalgorithmusa=rsa-sha256Ja
bSignature (Signatur)Tatsächliche Signatur (Base64)b=dzdVy...Ja
bhBody Hash (Body-Hash)E-Mail-Body-Hashbh=2jUSO...Ja
cCanonicalization (Kanonisierung)Kanonisierungsalgorithmusc=relaxed/relaxedNein
dDomainSignatur-Domaind=example.comJa
hHeadersSignierte Header-Felderh=From:To:SubjectJa
iIdentity (Identität)Unterzeichner-Identität[email protected]Nein
lBody Length (Body-Länge)Signierte Body-Längel=1000Nein
qQuery Method (Abfragemethode)Öffentlicher Schlüssel Abfragemethodeq=dns/txtNein
sSelector (Selektor)Selektors=defaultJa
tTimestamp (Zeitstempel)Signatur-Zeitstempelt=1234567890Nein
xExpire Time (Ablaufzeit)Ablaufzeitx=1234657890Nein
zCopied Headers (Kopierte Header)Originale Header-Kopiez=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

TagBeschreibungBeispielErforderlich
vVersionv=DKIM1Nein
kSchlüsseltypk=rsaNein
pÖffentlicher Schlüssel (Base64)p=MIGfMA0...Ja
hAkzeptable Hash-Algorithmenh=sha256Nein
sDiensttyps=emailNein
tFlagst=y (Testmodus)Nein
nNotizenn=NotesNein

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.