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
| Tag | Nome | Descrizione | Esempio | Richiesto |
|---|---|---|---|---|
v | Version (Versione) | Numero di versione | v=1 | Sì |
a | Algorithm (Algoritmo) | Algoritmo di firma | a=rsa-sha256 | Sì |
b | Signature (Firma) | Firma effettiva (Base64) | b=dzdVy... | Sì |
bh | Body Hash (Hash del corpo) | Hash del corpo della posta | bh=2jUSO... | Sì |
c | Canonicalization (Canonizzazione) | Algoritmo di canonizzazione | c=relaxed/relaxed | No |
d | Domain (Dominio) | Dominio di firma | d=example.com | Sì |
h | Headers | Campi header firmati | h=From:To:Subject | Sì |
i | Identity (Identità) | Identità del firmatario | [email protected] | No |
l | Body Length (Lunghezza corpo) | Lunghezza del corpo firmata | l=1000 | No |
q | Query Method (Metodo di query) | Metodo di query chiave pubblica | q=dns/txt | No |
s | Selector (Selettore) | Selettore | s=default | Sì |
t | Timestamp | Timestamp della firma | t=1234567890 | No |
x | Expire Time (Scadenza) | Tempo di scadenza | x=1234657890 | No |
z | Copied Headers (Header copiati) | Copia degli header originali | z=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
| Tag | Descrizione | Esempio | Richiesto |
|---|---|---|---|
v | Versione | v=DKIM1 | No |
k | Tipo di chiave | k=rsa | No |
p | Chiave pubblica (Base64) | p=MIGfMA0... | Sì |
h | Algoritmi hash accettabili | h=sha256 | No |
s | Tipo di servizio | s=email | No |
t | Flag | t=y (modalità test) | No |
n | Note | n=Notes | No |
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.