Kimlik Doğrulama ve Teslim Edilebilirlik İçin DKIM ile E-posta İmzalama

DKIM (DomainKeys Identified Mail, RFC 6376) bir alan adı sahibinin giden e-postaya kriptografik bir imza eklemesini sağlar. Gelen sunucular eşleşen genel anahtarı DNS’ten çeker, hash’i yeniden hesaplar ve iki şeyi aynı anda doğrular: mesaj gövdesi yolculukta değiştirilmemiştir ve mesaj gerçekten alan adı tarafından yetkilendirilmiş bir göndericiden gelmiştir. DKIM olmadan modern sağlayıcılar (Gmail, Microsoft 365, Yahoo) posta’yı spam olarak işaretler, sessizce düşürür ya da teslimatı reddeder — DKIM artık üretim postası için opsiyonel değildir.

Bu kılavuz, tam bir yürütme rehberidir Aspose.Email.NET için DKIM ad alanı. Bu makaledeki her kod bloğu, DKIMExamples projede ve kütüphaneyle uçtan uca doğrulanmıştır.

Önkoşullar

DKIM türleri şurada bulunur Aspose.Email.DKIM ad alanı ve yalnızca .NET Framework 4.0/4.5 kütüphane derlemeleriyle birlikte dağıtılır. .NET Standard 2.0 ve .NET 6/8 paketlerinden bilinçli olarak çıkarılmıştır. Aşağıdaki örnekleri izlemek için projenizin şu sürüm için derlenmiş bir Aspose.Email derlemesine başvurması gerekir net45 (hedef net48 tüketen içinde .csproj).

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net48</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="Aspose.Email">
      <HintPath>..\path\to\net45\Aspose.Email.dll</HintPath>
    </Reference>
  </ItemGroup>
</Project>

Ayrıca bir RSA anahtar çiftine ihtiyacınız var. Özel anahtar uygulamanızda tutulur ve imzalamak için kullanılır; genel anahtar bir DNS TXT kaydı olarak yayınlanır, böylece alıcılar doğrulayabilir. Bugün standart 2048-bit anahtardır:

openssl genrsa -out sample-private-key.pem 2048

Aspose’un PemReader hem PKCS#1 (-----BEGIN RSA PRIVATE KEY-----) ve PKCS#8 (-----BEGIN PRIVATE KEY-----) biçimlendirilmiş dosyalar.

| Sınıf | Rol | |——-|——| | DKIMSignatureInfo | İmzayı tanımlar: seçici, alan adı, hash algoritması, kanonlaştırma ve kapsanacak başlıklar. | | PemReader | PEM biçimli bir dosyadan veya akıştan bir RSA özel anahtarı yükler RSACryptoServiceProvider. | | MailMessage.DKIMSign(rsa, info) | Uzantı/örnek metod, mesajın imzalı bir kopyasını döndürür DKIM-Signature başlık dolduruldu. | | DkimVerifier | İmzalı bir mesajın DNS tabanlı doğrulamasını yapar — dosya, akış, senkron, veya asenkron. |

Tam akış her zaman: anahtar yükle → imzayı tanımla → mesaj oluştur → imzala → (isteğe bağlı) doğrula.

Minimum imzalama örneği

Geçerli bir DKIM imzalı mesaj üreten en küçük kod miktarı:

// Load private key from PEM file
var rsa = PemReader.GetPrivateKey(PrivateKeyPath);

// Create DKIM signature information
var signInfo = new DKIMSignatureInfo("dkim", "example.com")
{
    // Default: Relaxed/Relaxed canonicalization
    // Default: RSASha1 hash algorithm
};

// Add headers to sign
signInfo.Headers.Add("From");
signInfo.Headers.Add("To");
signInfo.Headers.Add("Subject");

// Create a test email
var mailMessage = new MailMessage(
    "sender@example.com",
    "recipient@example.com",
    "Test Subject",
    "This is a test email body signed with DKIM.")
{
    From = new MailAddress("sender@example.com", "Sender Name")
};

// Sign the message
var signedMsg = mailMessage.DKIMSign(rsa, signInfo);

// Save the signed message
string outputPath = Path.Combine(DataDir, "basic-signed.eml");
signedMsg.Save(outputPath);

Console.WriteLine($"DKIM-Signature header: {signedMsg.Headers["DKIM-Signature"]?.Substring(0, 50)}...");

İki argüman DKIMSignatureInfo"dkim" ve "example.com"seçici ve alan adıdır. Birlikte alıcılara genel anahtarınızı DNS TXT kaydı üzerinden nereden bulabileceklerini söyler: dkim._domainkey.example.com. Kısa bir seçici adı seçin (genellikle default, mail, ya da şu şekilde bir tarih 2026jan); seçici, eski anahtarı atmadan anahtarları döndürmenizi sağlar.

Kritik bir detay: DKIMSign bir yeni döndürür MailMessage instance with the DKIM-Signature başlık eklendi. Orijinal mesaj değişmedi. Girdi değil, döndürülen nesneyi kaydedin veya gönderin.

İmzalanacak başlıkların seçilmesi

Listelenen başlıklar içinde signInfo.Headers sadece değerleri imzaya bağlananlardır. Listelenmeyenler, imzayı bozmadan röleler tarafından değiştirilebilir; bu bazen istenen bir durumdur (örn. Received: başlıklar aşağı akışta eklenir) ve bazen tehlikeli olabilir (ör. bırakmak From imzasız olması imzayı anlamsız kılar).

Genel kural: her zaman imzalayın From, To, Subject, Date.** From özellikle DMARC uyumu için zorunludur.

var signInfo = new DKIMSignatureInfo("dkim2", "example.com")
{
    Headers =
    {
        "From",
        "To",
        "Subject",
        "Date",
        "MIME-Version"
    }
};

// Create email with additional headers
var mailMessage = new MailMessage(
    "sender@example.com",
    "recipient@example.com",
    "Custom Headers Test",
    "This message includes custom headers in the DKIM signature.")
{
    From = new MailAddress("sender@example.com", "Sender"),
    Date = DateTime.UtcNow
};

// Ensure headers listed in DKIM signature are present on the message
mailMessage.Headers.Add("MIME-Version", "1.0");
mailMessage.Headers.Add("X-Custom-Header", "CustomValue");

// Sign with custom headers
var signedMsg = mailMessage.DKIMSign(rsa, signInfo);

string outputPath = Path.Combine(DataDir, "custom-headers-signed.eml");
signedMsg.Save(outputPath);

Eğer listelerseniz Date ama asla ayarlamayın mailMessage.Date, DKIMSign fırlatır The following headers to be signed do not exist: Date. İlk önce başlıkları doldurun, ardından imzalayın.

Hash algoritmaları — RSA‑SHA1 vs RSA‑SHA256

DKIM iki imzalama algoritmasını destekler. Aspose.Email’de varsayılan RSASha1 geriye uyumluluk için, ancak her yeni dağıtım şunu kullanmalı RSASha256**. Birçok hizmet sağlayıcı artık SHA‑1 imzalarını kriptografik olarak doğrulansa bile geçersiz olarak işaretliyor.

// Example with RSASha1 (older, less secure but widely supported)
var signInfoSha1 = new DKIMSignatureInfo("dkim-sha1", "example.com")
{
    HashAlgorithm = DKIMHashAlgorithm.RSASha1,
    Headers = { "From", "To", "Subject" }
};

var mailMessage1 = new MailMessage(
    "sender@example.com",
    "recipient@example.com",
    "SHA1 Test",
    "Signed with RSASha1 algorithm.");

mailMessage1.From = new MailAddress("sender@example.com");
var signedMsgSha1 = mailMessage1.DKIMSign(rsa, signInfoSha1);

string pathSha1 = Path.Combine(DataDir, "sha1-signed.eml");
signedMsgSha1.Save(pathSha1);

// Example with RSASha256 (newer, more secure)
var signInfoSha256 = new DKIMSignatureInfo("dkim-sha256", "example.com")
{
    HashAlgorithm = DKIMHashAlgorithm.RSASha256,
    Headers = { "From", "To", "Subject" }
};

var mailMessage2 = new MailMessage(
    "sender@example.com",
    "recipient@example.com",
    "SHA256 Test",
    "Signed with RSASha256 algorithm.");

mailMessage2.From = new MailAddress("sender@example.com");
var signedMsgSha256 = mailMessage2.DKIMSign(rsa, signInfoSha256);

string pathSha256 = Path.Combine(DataDir, "sha256-signed.eml");
signedMsgSha256.Save(pathSha256);

Ortaya çıkan DKIM-Signature başlık algoritmayı şu yol ile ilan eder a= etiketi (a=rsa-sha1 vs a=rsa-sha256). Alıcılar bu etiketi hangi hash’in yeniden hesaplanacağını belirlemek için kullanır.

Kanonikleştirme — Basit vs Gevşek

Kanonikleştirme, DKIM’in bir mesajın hash’lenmeden önce kararlı, byte‑tam bir form üzerinde anlaşmasını sağlar. Başlıklar ve gövdeler için bağımsız olarak iki algoritma tanımlanmıştır:

  • Simple — neredeyse hiç normalizasyon yapmaz. Gövde sonundaki boş satırlar kaldırılır, fakat başka bir boşluk dokunulmaz. Bir röle tarafından eklenen tek bir boşluk bile imzayı geçersiz kılar.
  • Relaxed — başlık değerleri içinde boşlukları katlar, başlık adlarını küçültür ve gövde içindeki boşluk dizilerini daraltır. Mail taşıma ajanlarının yaptığı küçük yeniden biçimlendirmelere toleranslıdır.

Seçin Relaxed her iki başlık ve gövde için de, özel bir sebebiniz olmadıkça. Neredeyse tüm üretim dağıtımları bunu yapar.

// Simple canonicalization - almost no changes allowed
var signInfoSimple = new DKIMSignatureInfo("dkim-simple", "example.com")
{
    HeaderCanonicalization = CanonicalizationType.Simple,
    BodyCanonicalization = CanonicalizationType.Simple,
    Headers = { "From", "To", "Subject" }
};

var mailMessage1 = new MailMessage(
    "sender@example.com",
    "recipient@example.com",
    "Simple Canonicalization",
    "This message uses Simple canonicalization.");

mailMessage1.From = new MailAddress("sender@example.com");
var signedSimple = mailMessage1.DKIMSign(rsa, signInfoSimple);

string pathSimple = Path.Combine(DataDir, "simple-canonical.eml");
signedSimple.Save(pathSimple);

// Relaxed canonicalization - minor changes allowed (whitespace normalization)
var signInfoRelaxed = new DKIMSignatureInfo("dkim-relaxed", "example.com")
{
    HeaderCanonicalization = CanonicalizationType.Relaxed,
    BodyCanonicalization = CanonicalizationType.Relaxed,
    Headers = { "From", "To", "Subject" }
};

var mailMessage2 = new MailMessage(
    "sender@example.com",
    "recipient@example.com",
    "Relaxed Canonicalization",
    "This message uses Relaxed canonicalization.");

mailMessage2.From = new MailAddress("sender@example.com");
var signedRelaxed = mailMessage2.DKIMSign(rsa, signInfoRelaxed);

string pathRelaxed = Path.Combine(DataDir, "relaxed-canonical.eml");
signedRelaxed.Save(pathRelaxed);

Başlık ve gövde kanonikleştirmesi bağımsızdır — relaxed/simple tamamen geçerli bir kombinasyondur, şu şekilde yazılmıştır c= DKIM başlığının etiketi.

Özel anahtarın yüklenmesi

PemReader PEM kodlamalarını şeffaf bir şekilde işler. Ayrı bir Pkcs1Reader / Pkcs8Reader — sadece yolu ya da akışı iletin:

// Check if private key file exists
if (!File.Exists(PrivateKeyPath))
{
    Console.WriteLine($"Private key file not found: {PrivateKeyPath}");
    Console.WriteLine("Creating a sample private key for demonstration...");

    // Create a sample RSA key (for testing only - in production, use real keys)
    using var rsa = new System.Security.Cryptography.RSACryptoServiceProvider(2048);
    var privateKeyParams = rsa.ExportParameters(true);

    Console.WriteLine("Sample RSA key created (2048 bits).");
    Console.WriteLine("In production, load your actual private key from a secure location.");
    Console.WriteLine("PEM file format should contain either:");
    Console.WriteLine("  - BEGIN RSA PRIVATE KEY (PKCS#1)");
    Console.WriteLine("  - BEGIN PRIVATE KEY (PKCS#8)");
}
else
{
    // Load the private key
    var rsa = PemReader.GetPrivateKey(PrivateKeyPath);
    Console.WriteLine($"Private key loaded successfully from: {PrivateKeyPath}");
    Console.WriteLine("Key size: 2048 bits (typical for DKIM)");
}

Console.WriteLine("\nPEM File Format Examples:");
Console.WriteLine("--------------------------");
Console.WriteLine("PKCS#1 format:");
Console.WriteLine("-----BEGIN RSA PRIVATE KEY-----");
Console.WriteLine("MIIEpAIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8PbnGy...");
Console.WriteLine("-----END RSA PRIVATE KEY-----");
Console.WriteLine();
Console.WriteLine("PKCS#8 format:");
Console.WriteLine("-----BEGIN PRIVATE KEY-----");
Console.WriteLine("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...");
Console.WriteLine("-----END PRIVATE KEY-----");

PemReader.GetPrivateKey bir RSACryptoServiceProvider kullanıma hazır DKIMSign. Ayrıca bir GetPrivateKey(Stream) anahtarın bir anahtar kasasından, şifrelenmiş blob’dan veya gömülü kaynaktan geldiği durumlar için aşırı yükleme.

Anahtar yönetimi kuralları:

  1. Özel anahtarları asla sürüm kontrolüne commit etmeyin. *.pem şunun içinde yer alır .gitignore.
  2. Üretimde, anahtarları bir gizli yönetici (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault) üzerinden alın, diske kaydetmeyin.
  3. Anahtarları her 6–12 ayda bir döndürün. Yeni seçiciyi önce yayımlayın, imzalamayı değiştirin, ardından bir grace süresinden sonra eski DNS kaydını kaldırın.

Bir dosyadan imzalı bir mesajı doğrulama

Doğrulama, eşdeğer bir işlemdir: imzalı bir mesaj verildiğinde .eml, DkimVerifier öğeyi çıkartır DKIM-Signature başlıktır, şuna karşı bir DNS sorgusu yapar <selector>._domainkey.<domain> genel anahtarı alır ve özeti yeniden hesaplar. Sonuç bir DkimResult ile IsValid ve ErrorMessage.

// For this example, we need a signed message
if (!File.Exists(signedMessagePath))
{
    Console.WriteLine($"Signed message file not found: {signedMessagePath}");
    Console.WriteLine("Please provide a DKIM-signed .eml file for verification.");
    return;
}

// Verify the signature
DkimResult result = DkimVerifier.Verify(signedMessagePath);

// Check the result
if (result.IsValid)
{
    Console.WriteLine("[OK] DKIM signature verification successful!");
    Console.WriteLine("The message was signed by the claimed domain.");
}
else
{
    Console.WriteLine("[FAIL] DKIM signature verification failed!");
    Console.WriteLine($"Error: {result.ErrorMessage}");
}

Console.WriteLine("\nVerification Process:");
Console.WriteLine("1. Extracts DKIM-Signature header from the message");
Console.WriteLine("2. Queries DNS for the public key (selector._domainkey.domain)");
Console.WriteLine("3. Verifies the signature using the public key");
Console.WriteLine("4. Compares computed body hash with the signed body hash");

ile imzalanmış bir mesajı doğrulama d=example.com "sözlükte anahtar yok" ya da benzeri bir DNS‑sorgu hatasıyla her zaman başarısız olur, çünkü example.com bir DKIM kaydı yayımlamaz. Gerçek doğrulama testleri için kontrolünüzdeki bir alanı (veya bilinen iyi bir test mesajını) kullanın.

Akıştan Doğrulama

Mesaj diskte değilse — bir IMAP getirmesinden, bir kuyruktan, bir HTTP yüklemesinden veya blob depolamadan gelmişse — Stream aşırı yükleme. Herhangi bir arama yapılabilir okuma akışı kabul eder:

// Load the signed message into a stream
using (var stream = File.OpenRead(signedMessagePath))
{
    DkimResult result = DkimVerifier.Verify(stream);

    if (result.IsValid)
    {
        Console.WriteLine("[OK] DKIM signature verified successfully from stream!");
    }
    else
    {
        Console.WriteLine("[FAIL] Verification failed!");
        Console.WriteLine($"Error: {result.ErrorMessage}");
    }
}

Console.WriteLine("\nStream-based verification is useful when:");
Console.WriteLine("  - Working with email from IMAP/POP3 clients");
Console.WriteLine("  - Processing emails from databases or cloud storage");
Console.WriteLine("  - Building web applications that receive emails via HTTP");

Verify(Stream) ve Verify(string) fonksiyonel olarak aynıdır — her ikisi de DNS sorgusu tamamlanana kadar engeller. Kullanıcıya yönelik her şey için aşağıdaki asenkron varyantları kullanın.

Asenkron doğrulama

DKIM doğrulaması bir DNS turu içerir; bu da hızlı bir ağda bile onlarca milisaniye sürebilir. Bir ASP.NET istek işleyicisi, mesaj işleme döngüsü veya herhangi bir UI iş parçacığında bu hızla birikir. VerifyAsync bir Task<DkimResult> ki await:

if (!File.Exists(signedMessagePath))
{
    Console.WriteLine("Signed message file not found.");
    return;
}

// Async verification
DkimResult result = await DkimVerifier.VerifyAsync(signedMessagePath);

if (result.IsValid)
{
    Console.WriteLine("[OK] Async verification successful!");
}
else
{
    Console.WriteLine("[FAIL] Async verification failed!");
    Console.WriteLine($"Error: {result.ErrorMessage}");
}

Console.WriteLine("\nAsync verification is recommended for:");
Console.WriteLine("  - Web applications (ASP.NET, etc.)");
Console.WriteLine("  - Services that process multiple emails");
Console.WriteLine("  - Any application where you want to avoid blocking the main thread");

Ayrıca bir VerifyAsync(Stream) aşırı yükleme. Senkron API ile aynı şekilde, yöntem şu bildirimi yapar async Task (değil async void) — çağrı noktaları şunlar olmalı await ve konsol uygulaması giriş noktaları şunu yapabilir VerifySignatureAsync().GetAwaiter().GetResult() bir async Main kullanılamıyor.

Uçtan uca: yükle, imzala, kaydet, doğrula

Yukarıdaki parçalar tek bir iş akışına birleşir. Bu, işlem e‑posta hizmetine gömeceğiniz tam imzala‑ve‑doğrula hattıdır:

// Step 1: Load private key
var rsa = PemReader.GetPrivateKey(PrivateKeyPath);

// Step 2: Create signature information
var signInfo = new DKIMSignatureInfo("dkim", "example.com")
{
    HeaderCanonicalization = CanonicalizationType.Relaxed,
    BodyCanonicalization = CanonicalizationType.Relaxed,
    HashAlgorithm = DKIMHashAlgorithm.RSASha256,
    Headers =
    {
        "From",
        "To",
        "Subject",
        "Date",
        "MIME-Version"
    }
};

// Step 3: Create email message
var mailMessage = new MailMessage(
    "john.doe@example.com",
    "jane.smith@example.org",
    "Important Document - DKIM Signed",
    "Please find the attached document.\n\nBest regards,\nJohn Doe");

mailMessage.From = new MailAddress("john.doe@example.com", "John Doe");
mailMessage.To.Add(new MailAddress("jane.smith@example.org", "Jane Smith"));
mailMessage.Date = DateTime.UtcNow;
mailMessage.Headers.Add("MIME-Version", "1.0");

// Step 4: Sign the message
var signedMessage = mailMessage.DKIMSign(rsa, signInfo);

// Step 5: Save the signed message
string signedPath = Path.Combine(DataDir, "end-to-end-signed.eml");
signedMessage.Save(signedPath);

// Step 6: Verify the signature
var result = DkimVerifier.Verify(signedPath);

if (result.IsValid)
{
    Console.WriteLine("  [OK] Signature verification successful!");
    Console.WriteLine("  [OK] The message integrity is confirmed.");
    Console.WriteLine("  [OK] The sender domain is authenticated.");
}
else
{
    Console.WriteLine($"  [FAIL] Verification failed: {result.ErrorMessage}");
}

// Step 7: Display DKIM-Signature header
Console.WriteLine("\nStep 7: DKIM-Signature header:");
Console.WriteLine("  " + signedMessage.Headers["DKIM-Signature"]);

Tam bir DKIM-Signature bu kod tarafından üretilen başlık şu şekilde görünür:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com;
 h=from:to:subject:date:mime-version; q=dns/txt; s=dkim; t=1780252880;
 bh=e2RkwEm0k0yLcVmhi3KeXpt14Ppx3Xx49S33/+J8OGc=;
 b=Zd3sfCGGD9YdOLw1o3YHLFs7DO6V9cc7anye5E3kQ/MEdkU14daTru0sXTZCtCryvJubCs...

İlginç etiketler:

  • v=1 — DKIM sürümü.
  • a=rsa-sha256 — algoritma; yansıtıyor HashAlgorithm.
  • c=relaxed/relaxed — kanonikleştirme (başlık/gövde).
  • d=example.com / s=dkim — seçici ve alan; birlikte DNS sorgu adını oluştururlar.
  • h=from:to:subject:date:mime-version — sırayla imzalı başlıklar.
  • bh= — kanonikleştirilmiş gövdenin base64 özeti.
  • b= — kanonikleştirilmiş başlıklar üzerindeki gerçek RSA imzası.

İmzalı bir başlık değerini değiştirirseniz ya da gövdeyi değiştirirseniz, bh ve b artık eşleşmez ve doğrulama başarısız olur.

DNS’te genel anahtarın yayımlanması

Yerel olarak imzalamak sadece işin yarısıdır. Alıcıların doğrulaması için RSA anahtarınızın genel karşılığı bir DNS TXT kaydı olarak yayımlanmalıdır: <selector>._domainkey.<domain>:

Record: dkim._domainkey.example.com
Type:   TXT
Value:  v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBC...

OpenSSL ile PEM dosyanızdan genel anahtarı çıkarın:

openssl rsa -in sample-private-key.pem -pubout -out sample-public-key.pem

Başlık/footer satırlarını kaldırın ve temel64 içeriği birleştirerek p= değer. Kayıt canlı olduğundan emin olduktan sonra ona güvenin:

dig dkim._domainkey.example.com TXT

Online araçlar gibi DKIM Doğrulayıcısı ve MXToolbox DKIM Kontrolcüsü gerçek bir mesajın uçtan uca doğrulanabilir olup olmadığını size söyleyecektir. Üretim trafiğine geçmeden önce bunları kullanın.