Uygulamanız kusursuz bir DKIM-Signature başlığı üretti. Anahtar 2048-bit, seçici DNS'te yayında, bh= doğru hesaplandı. Yine de alıcı tarafta Authentication-Results başlığında acı gerçek yazıyor: dkim=fail (body hash did not verify). Sebep neredeyse her zaman aynıdır: imzayı siz hesapladıktan sonra biri mesajı değiştirdi.
DKIM'i nereye koyacağınız — uygulama koduna mı, MTA'ya mı — bir zevk meselesi değil, bir zamanlama meselesidir. Bu yazı "hangisi daha kolay" sorusuna değil, "mesajı telin üzerine çıkmadan önce en son kim değiştiriyor" sorusuna cevap verir. Peşinen net öneri: varsayılan olarak MTA'da, son milter olarak imzalayın. Uygulama katmanına yalnızca somut bir gerekçeniz varsa geçin. Nedenini gösterelim.
Kararı belirleyen tek soru: mesajı en son kim değiştiriyor?
DKIM iki ayrı şey imzalar: seçtiğiniz başlıkların bir hash'i ve gövdenin hash'i (bh= etiketi). Başlık imzası, h= içinde saydığınız başlıkları kriptografik olarak mühürler. Gövde hash'i ise mesaj gövdesinin tek bir byte'ına bile bağlıdır. Bir boşluk, bir satır sonu, bir yeniden kodlama — bh= çöker ve imza baştan geçersizdir.
Bu kırılganlık bir tasarım hatası değil, bütün mekanizmanın amacı. Ama pratik bir sonucu var: imza her zaman tele en yakın noktada, mesaj üzerindeki tüm dönüşümler bittikten sonra hesaplanmalı. Buna "geç imzala, en son imzala" (sign late, sign last) diyorum; bu yazının omurgası bu tek cümle.
Canonicalization (kanonikleştirme) bu kırılganlığı biraz yumuşatır. c= etiketi başlık ve gövde için ayrı ayrı simple ya da relaxed alır. simple neredeyse hiç toleranslı değildir: bir başlığın katlanması (folding) ya da fazladan bir boşluk imzayı bozar. relaxed ise ardışık boşlukları tek boşluğa indirger, satır sonundaki boşlukları kırpar. Modern kurulumların tamamı c=relaxed/relaxed üretmeli. simple/simple üreten bir sistem, yolda mesaja dokunan her ara sunucuda kırılmaya adaydır.
Aşağıdaki diyagram, imzayı nerede hesapladığınızın sonucunu tek bakışta gösteriyor. Kritik olan, imza noktası ile "tel" arasında kalan bölge — orada mesaja dokunan herhangi bir bileşen imzanızı sessizce öldürür.
MTA'da imzalama: son milter olarak en sağlam yol
Kendi Postfix'inizi işletiyorsanız ve tek ya da birkaç alan adınız varsa, tartışma burada biter: imzayı MTA'ya bırakın. Mesajı artık değiştirmeyeceği anda imzalar, dolayısıyla mutasyon bölgesi sıfırlanır.
İki yaygın seçenek var: OpenDKIM ve rspamd. OpenDKIM klasiktir, tek işi imzalamaktır. Önce anahtarı üretin:
opendkim-genkey -b 2048 -s mail -d evilmail.pro
# mail.private (özel anahtar) + mail.txt (DNS TXT kaydı) üretirmail.txt içindeki kaydı DNS'e koyarsınız — dikkat, 2048-bit anahtarın base64'ü 255 karakteri aşar, bu yüzden TXT çok-string olarak bölünmüş gelir:
mail._domainkey.evilmail.pro. IN TXT ( "v=DKIM1; k=rsa; h=sha256; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
"...QIDAQAB" )DNS panelinizde bunu tek string'e sıkıştırıp 255 karakter sınırını aşarsanız kayıt sessizce kırılır — canlıda en sık gördüğüm hata bu. OpenDKIM tarafında KeyTable, SigningTable ve TrustedHosts üçlüsünü kurar, sonra Postfix'e milter olarak bağlarsınız:
# /etc/postfix/main.cf
smtpd_milters = inet:localhost:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = acceptKritik nokta milter sırası. Spam filtreniz veya başlık yeniden yazan başka bir milter varsa, bunlar DKIM'den önce gelmeli. smtpd_milters soldan sağa çalışır; DKIM en sonda olmalı ki kendisinden önceki milter'lar mesajı değiştirmeyi bitirsin. Aksi halde tekrar mutasyon bölgesine düşersiniz.
Birden çok alan adı yönetiyorsanız rspamd daha az bakım ister. dkim_signing modülü, From başlığındaki alan adına göre anahtarı otomatik seçer:
# /etc/rspamd/local.d/dkim_signing.conf
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector = "mail";
allow_hdrfrom_mismatch = false;
use_domain = "header";use_domain = "header" satırı DMARC hizalaması için hayati: imzayı gönderen zarf adresine göre değil, From: başlığındaki alan adına göre atar. DMARC tam olarak buna bakar.
Yeni kuruyorsanız Ed25519 + RSA çift seçiciyi de değerlendirin (RFC 8463). Ed25519 anahtarı çok daha kısadır (k=ed25519; p=<base64>), ama eski doğrulayıcılar tanımaz. İki seçiciyle imzalarsınız: yeni doğrulayıcılar Ed25519'a, eskiler RSA'ya düşer.
Uygulama katmanında imzalama: ne zaman gerçekten gerekir
Uygulama katmanı varsayılan değil, istisnadır. Ama gerçek gerekçeler var:
- Çok kiracılı SaaS. Her müşteri kendi alan adıyla (
d=musteri.com) gönderiyor ve her biri için ayrı anahtar tutuyorsunuz. Milter'ı her yeni müşteride yeniden yapılandırmak yerine, imzayı uygulamadan atmak operasyonel olarak daha temiz. - Programatik anahtar rotasyonu. Anahtarları bir API üzerinden döndürmek, seçiciyi çalışma anında değiştirmek istiyorsanız, uygulama katmanı bunu milter yeniden başlatmadan yapar.
- Kontrol etmediğiniz bir relay. SES ya da SendGrid kendi imzasını ekler, ama siz kendi
d='nizle de imzalamak isteyebilirsiniz. Mesajı relay'e teslim etmeden önce uygulamada imzalarsınız.
Node tarafında nodemailer'ın yerleşik DKIM desteği var:
const transporter = nodemailer.createTransport({
host: "127.0.0.1",
port: 25,
dkim: {
domainName: "evilmail.pro",
keySelector: "mail",
privateKey: fs.readFileSync("/etc/dkim/evilmail.pro.mail.key", "utf8"),
// headerFieldNames: "from:to:subject:date:message-id",
},
});Python tarafında dkimpy aynı işi görür. Ama burada dev bir tuzak var ve doğrudan bir sonraki bölüme köprü kuruyor: nodemailer mesajı imzalayıp 127.0.0.1:25 üzerinden Postfix'e teslim ederse ve Postfix Content-Transfer-Encoding'i yeniden yazarsa (8bit → quoted-printable), bh= anında çöker. Uygulamada imzalıyorsanız, imzalı mesajı Postfix'in dokunmayacağı bir yoldan göndermek zorundasınız — ya da imzayı en baştan MTA'ya bırakmak.
Sessiz katiller: imzayı bozan dönüşümler
dkim=fail gördüğünüzde suçlu genelde şu altı şeyden biridir:
- Content-Transfer-Encoding yeniden yazımı.
8bitgövdeyiquoted-printable'a çeviren her ara sunucu gövde byte'larını değiştirir,bh=çöker. En yaygın "temiz imza, fail sonuç" nedeni. - `always_add_missing_headers = yes`. Postfix eksik
DateveyaMessage-IDbaşlığını ekler. Uygulama bu başlığı zaten yazdıysa çifte başlık oluşur;Fromgibi bir başlığıh='e koyup mesaja iki kez sokmak da başlık imzasını bozar. - Başlık katlama ve whitespace.
simplekanonikleştirmeyle en ufak folding farkı imzayı öldürür.relaxedkullanma sebebiniz bu. - `l=` gövde uzunluk etiketi. Gövdenin yalnızca ilk N byte'ını imzalar. Kulağa esnek gelir ama bir güvenlik açığıdır: saldırgan imzalı gövdenin sonuna içerik ekleyebilir (append attack) ve imza hâlâ geçerli görünür. Asla kullanmayın.
Hibrit: çift imza ve ARC
En sofistike kurulum ikisini birden kullanır. Uygulama müşteri alan adıyla imzalar (d=musteri.com), MTA kendi altyapı alan adınızla ikinci bir DKIM-Signature ekler (d=evilmail.pro). Bir mesajın birden çok DKIM imzası taşıması RFC'ce tamamen geçerlidir; doğrulayıcı her birini bağımsız kontrol eder. DMARC için önemli olan, en az bir geçen imzanın From: ile hizalı olması.
Yönlendirme ve mailing-list senaryolarında hikaye değişir. Bir liste sunucusu Subject'e [liste] etiketi ekler ya da altbilgi yapıştırırsa, gövde değiştiği için orijinal DKIM imzası zorunlu olarak kırılır. İşte ARC (Authenticated Received Chain) burada devreye girer: her ara sunucu, mesajı aldığı andaki doğrulama sonucunu kriptografik olarak "mühürler" ve zincire ekler. ARC, DKIM'in yerini almaz; kırılan imzanın olduğu yerde "ben aldığımda bu mesaj geçerliydi" güvencesini taşır. DMARC'ı ARC üzerinden geçirmek alıcının politikasına kalmıştır, ama zinciri hiç sağlamamak forward edilen postanın deliverability'sini öldürür.
Karar ağacı
Doğrulama ve hata ayıklama
İmza atmadan önce DNS'in doğru yayınlandığını görün:
dig +short TXT mail._domainkey.evilmail.pro
opendkim-testkey -d evilmail.pro -s mail -vvv
# "key OK" görmek istiyorsunuz; "key not secure" DNSSEC uyarısıdır, sorun değilSonra gerçek bir mesaj gönderin ve alıcı tarafta başlığı okuyun:
swaks --to [email protected] --from [email protected] --server 127.0.0.1Gmail'de "Show original" ile ya da alıcının Authentication-Results başlığında beklediğiniz çıktı:
Authentication-Results: mx.google.com;
dkim=pass header.d=evilmail.pro header.s=mail;
spf=pass ...; dmarc=passdkim=fail (body hash did not verify) görürseniz teşhis nettir: imzadan sonra gövde değişti — mutasyon bölgesine bakın. dkim=fail (signature did not verify) ise başlık tarafında sorun var ya da DNS'teki p= anahtarı özel anahtarınızla eşleşmiyor. Hızlı bir genel sağlık kontrolü için mail-tester.com da işe yarar, ama nihai gerçek her zaman alıcı MX'in Authentication-Results başlığıdır.
Üretime çıkmadan önce kontrol listesi
- Anahtar en az 2048-bit RSA; mümkünse Ed25519 ile çift seçici imza kurun.
c=relaxed/relaxedüretin;l=etiketi yok;h=içindeFrommutlaka var.- İmza tüm mesaj dönüşümlerinden sonra, tele en yakın noktada atılıyor.
- Postfix eksik başlık eklemiyor (
always_add_missing_headersdurumunu kontrol edin) ya da uygulamaDate/Message-ID'yi önceden yazıyor. d=↔From:DMARC hizası doğru (use_domain = "header").- DNS TXT kaydı 255-karakter parçalara doğru bölünmüş;
opendkim-testkey
İmzayı doğru katmana koymak, deliverability'nin en ucuz ama en çok atlanan kazanımıdır. Kural tek cümleye sığıyor: mesaja en son kim dokunuyorsa, imzayı o atmalı.


