Passa al contenuto principale

RFC 6376 - Firme DKIM

Informazioni di base

  • Numero RFC: 6376
  • Titolo: DomainKeys Identified Mail (DKIM) Signatures
  • Titolo italiano: Firme di posta identificata con chiavi di dominio
  • Data di pubblicazione: Settembre 2011
  • Stato: STANDARD PROPOSTO (PROPOSED STANDARD)
  • Autori: D. Crocker, T. Hansen, M. Kucherawy

Sommario (Abstract)

DomainKeys Identified Mail (DKIM) è un metodo di autenticazione della posta elettronica a livello di dominio. Permette alle organizzazioni di assumersi la responsabilità dei messaggi di posta elettronica utilizzando firme crittografiche. I destinatari possono verificare la firma per confermare che la posta proviene effettivamente dal dominio dichiarato e che il suo contenuto non è stato alterato.

Panoramica di DKIM

Cos'è DKIM?

Definizione:

DKIM = DomainKeys Identified Mail
Funzione: Firma digitale della posta elettronica
Scopo:
✓ Verificare il dominio del mittente
✓ Rilevare la manomissione della posta
✓ Prevenire phishing e falsificazione

Concetto fondamentale:
Il mittente firma con chiave privata → Il destinatario verifica con chiave pubblica

Problemi di sicurezza della posta elettronica:

Problemi del protocollo SMTP:
❌ Può falsificare l'indirizzo del mittente
❌ Non può verificare l'autenticità della posta
❌ Facilmente sfruttabile per phishing

Esempio di phishing:
From: [email protected] (falsificato)
In realtà: Server dell'attaccante

Soluzione DKIM:
✓ Il proprietario del dominio firma la posta
✓ Pubblicare la chiave pubblica nel DNS
✓ Il destinatario verifica la firma
✓ Firma fallita → rifiutare o contrassegnare

Flusso di lavoro DKIM

Lato invio (example.com):
1. Il server di posta genera la firma
- Firma gli header e il corpo della posta
- Utilizza la chiave privata (segreta)

2. Aggiungere la firma all'header della posta
DKIM-Signature: v=1; a=rsa-sha256; d=example.com; ...

3. Inviare la posta normalmente

Lato ricezione:
1. Estrarre l'header DKIM-Signature

2. Recuperare la chiave pubblica dal DNS
Query: default._domainkey.example.com

3. Verificare la firma
- Ricalcolare l'hash della posta
- Decifrare la firma con la chiave pubblica
- Confrontare i valori hash

4. Risultato:
✓ Firma valida → Da example.com, non manomessa
✗ Firma non valida → Falsificata o modificata

Dettagli dell'header DKIM-Signature

Formato di base

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...

Descrizione dei tag

TagNomeDescrizioneEsempioRichiesto
vVersion (Versione)Numero di versionev=1
aAlgorithm (Algoritmo)Algoritmo di firmaa=rsa-sha256
bSignature (Firma)Firma effettiva (Base64)b=dzdVy...
bhBody Hash (Hash del corpo)Hash del corpo della postabh=2jUSO...
cCanonicalization (Canonizzazione)Algoritmo di canonizzazionec=relaxed/relaxedNo
dDomain (Dominio)Dominio di firmad=example.com
hHeadersCampi header firmatih=From:To:Subject
iIdentity (Identità)Identità del firmatario[email protected]No
lBody Length (Lunghezza corpo)Lunghezza del corpo firmatal=1000No
qQuery Method (Metodo di query)Metodo di query chiave pubblicaq=dns/txtNo
sSelector (Selettore)Selettores=default
tTimestampTimestamp della firmat=1234567890No
xExpire Time (Scadenza)Tempo di scadenzax=1234657890No
zCopied Headers (Header copiati)Copia degli header originaliz=From:...No

Dettagli dei parametri chiave:

a (Algorithm - Algoritmo):

Algoritmi supportati:
rsa-sha256 (raccomandato)
rsa-sha1 (obsoleto, non sicuro)

Esempio:
a=rsa-sha256

c (Canonicalization - Canonizzazione):

Formato: c=<header>/<body>

Algoritmi:
- simple: Corrispondenza rigorosa
- relaxed: Corrispondenza flessibile (ignora spazi, maiuscole/minuscole, ecc.)

Esempi:
c=relaxed/relaxed (raccomandato, tollera modifiche del server di posta)
c=simple/simple (rigoroso, qualsiasi modifica causa fallimento della verifica)

d (Domain - Dominio):

Dominio di firma (deve essere il dominio del mittente o un dominio genitore)

Valido:
From: [email protected]
d=example.com ✓ (dominio genitore)

Non valido:
From: [email protected]
d=other.com ✗ (dominio non correlato)

s (Selector - Selettore):

Selettore: Utilizzato per query DNS

Scopo:
- Supportare più chiavi
- Rotazione delle chiavi
- Servizi diversi utilizzano chiavi diverse

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

h (Headers):

Campi header firmati (separati da due punti)

Raccomandato firmare:
- From (richiesto!)
- To
- Subject
- Date
- Message-ID

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

Nota:
Il campo From deve essere incluso!

Record di chiave pubblica DNS

Formato record TXT

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

Contenuto record TXT:
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC...

Descrizione tag:
v=DKIM1 Versione
k=rsa Tipo di chiave
p=<chiave pubblica> Chiave pubblica codificata Base64

Dettagli dei tag della chiave pubblica

TagDescrizioneEsempioRichiesto
vVersionev=DKIM1No
kTipo di chiavek=rsaNo
pChiave pubblica (Base64)p=MIGfMA0...
hAlgoritmi hash accettabilih=sha256No
sTipo di servizios=emailNo
tFlagt=y (modalità test)No
nNoten=NotesNo

Esempio completo:

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

Note:
- Può essere diviso in più stringhe (limite DNS 255 byte)
- Gli spazi sono ignorati
- Tag p vuoto → chiave revocata

Esempi di implementazione

Generare una coppia di chiavi

# Generare una chiave privata RSA a 2048 bit
openssl genrsa -out dkim_private.pem 2048

# Estrarre la chiave pubblica
openssl rsa -in dkim_private.pem -pubout -out dkim_public.pem

# Convertire in formato DNS (rimuovere header e newline)
cat dkim_public.pem | grep -v "BEGIN\|END" | tr -d '\n'

Output:

Chiave privata (dkim_private.pem):
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
-----END RSA PRIVATE KEY-----

Chiave pubblica (dkim_public.pem):
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----

Formato DNS:
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...

Implementazione della firma Node.js

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

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

// Firmare la posta elettronica
signEmail(headers, body) {
// 1. Canonizzare il corpo
const canonicalBody = this.canonicalizeBody(body);

// 2. Calcolare l'hash del corpo
const bodyHash = crypto
.createHash('sha256')
.update(canonicalBody)
.digest('base64');

// 3. Costruire l'header DKIM (senza tag b)
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=' // Firma vuota, da riempire dopo
].join('; ');

// 4. Canonizzare gli header
const canonicalHeaders = this.canonicalizeHeaders(headers, dkimHeader);

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

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

// Canonizzare il corpo (relaxed)
canonicalizeBody(body) {
return body
.replace(/[ \t]+(\r\n)/g, '$1') // Rimuovere spazi finali
.replace(/(\r\n)+$/, '\r\n'); // Mantenere solo un newline finale
}

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

// Aggiungere gli header da firmare
for (const name of headersToSign) {
const value = headers[name];
if (value) {
canonical += name + ':' + value.trim() + '\r\n';
}
}

// Aggiungere l'header DKIM-Signature (senza il valore del tag b)
canonical += 'dkim-signature:' +
dkimHeader.split('b=')[0] + 'b=';

return canonical;
}
}

// Utilizzo
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);

Implementazione della verifica

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

// 2. Ottenere la chiave pubblica
const publicKey = await this.getPublicKey(tags.d, tags.s);

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

// 4. Verificare la firma
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);

// Analizzare il record TXT
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) {
// Estrarre il valore della firma
const signature = tags.b;

// Ricostruire i dati della firma
const canonicalHeaders = this.canonicalizeHeaders(
headers,
dkimSignature.split('b=')[0] + 'b='
);

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

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

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

if (result.valid) {
console.log(`✓ Firma DKIM valida da ${result.domain}`);
} else {
console.log(`✗ Firma DKIM non valida: ${result.reason}`);
}

Best practice

1. Gestione delle chiavi

Lunghezza della chiave:
✓ Almeno 2048 bit
✓ Raccomandato 2048 o 3072 bit
✗ Evitare 1024 bit (non sicuro)

Rotazione delle chiavi:
- Cambiare le chiavi ogni 1-2 anni
- Pubblicare chiavi nuove e vecchie contemporaneamente (usare selettori diversi)
- Rimuovere le vecchie chiavi dopo il periodo di transizione

Nomenclatura dei selettori:
✓ Per data: 2024-01
✓ Per scopo: mailserver1
✓ Per rotazione: key1, key2

2. Selezione degli header

// Header raccomandati da firmare
const recommendedHeaders = [
'From', // Richiesto!
'To',
'Subject',
'Date',
'Message-ID',
'Reply-To',
'Cc',
'MIME-Version',
'Content-Type'
];

// Evitare di firmare header volatili
const avoidHeaders = [
'Received', // Aggiunto da ogni relay
'Return-Path', // Può cambiare in transito
'X-*' // Gli header personalizzati possono essere modificati
];

3. Modalità test

Aggiungere il flag t=y al record DNS:
v=DKIM1; k=rsa; t=y; p=MIGfMA...

Scopo:
- Indica la modalità test
- Il fallimento della verifica non rifiuterà la posta
- Utilizzato per test di distribuzione iniziale

Riferimenti

RFC relativi a DKIM:

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

Standard correlati:

  • [RFC 7489] DMARC (Basato su DKIM e SPF)
  • [RFC 7208] SPF (Sender Policy Framework)

Sommario: DKIM fornisce autenticazione a livello di dominio per la posta elettronica attraverso la tecnologia di firma digitale, formando la base della sicurezza moderna della posta elettronica. Un'implementazione corretta di DKIM può migliorare significativamente la recapitabilità della posta, ridurre i falsi positivi come spam e gettare le basi per meccanismi di sicurezza della posta elettronica più avanzati come DMARC.