Skip to content

Sending a DKIM Signed Email from C#

First off this code is intended to be instructional only! Yes it is possible to send a DKIM email from .net’s SmtpClient. But there are a few hacks/warnings to go with the following code. I’m pretty sure DKIM is designed for signatures to be added by the STMP server. All the emails I’ve seen with DKIM signatures sign headers that would only be available to the server, e.g: Message-ID and Received. What does the RFC 4871 have to say about this:

Note that verifiers may treat unsigned header fields with extreme skepticism, including refusing to display them to the end user or even ignoring the signature if it does not cover certain header fields. For this reason, signing fields present in the message such as Date, Subject, Reply-To, Sender, and all MIME header fields are highly advised.

Second: We need to sign the message body exactly as SmtpClient is going to deliver it to the SMTP server. Unfortunately MailMessage doesn’t give us access to this. I’ve found some code to do the Quoted-printable encoding here: Nice Clean C# Generate Quoted-Printable. Still we couldn’t guarantee we are going to hash the body exactly as SmtpClient is going to send. In this example I’m just replacing the carriage returns for an extremely simple sample email.

Let’s get started. First use OpenSSL to generate your public and private keys: DomainKeys Public/Private Key-pair Generation. Now add the public key to the relevant TXT record on the domain you intend to send email from. I bought a cheap .info domain for this demo, and pointed the registrar to ZoneEdit. I had no trouble adding to TXT record to my domain via ZoneEdit.

…and now the code: (again I’m using Bouncy Castle Crypto APIs to create the signature)

string smtp = "~~ YOUR STMP SERVER HERE ~~";
string from = "russ@dkimtester.info";
string subject = "dkim test email";
string to = "check-auth@verifier.port25.com";
string body = "This is the body of the message." + Environment.NewLine + "This is the second line";
string base64privatekey = @"-----BEGIN RSA PRIVATE KEY-----
~~ YOUR PRIVATE KEY HERE ~~
-----END RSA PRIVATE KEY-----";

HashAlgorithm hash = new SHA256Managed();
// HACK!! simulate the quoted-printable encoding SmtpClient will use
string hashBody = body.Replace(Environment.NewLine, "=0D=0A") + Environment.NewLine;
byte[] bodyBytes = Encoding.ASCII.GetBytes(hashBody);
string hashout = Convert.ToBase64String(hash.ComputeHash(bodyBytes));
// timestamp  - seconds since 00:00:00 on January 1, 1970 UTC
TimeSpan t = DateTime.Now.ToUniversalTime() - DateTime.SpecifyKind(DateTime.Parse("00:00:00 January 1, 1970"), DateTimeKind.Utc);

string signatureHeader = "v=1; " +
 "a=rsa-sha256; " +
 "c=relaxed/relaxed; " +
 "d=dkimtester.info; " +
 "s=p; " +
 "t=" + Convert.ToInt64(t.TotalSeconds) + "; " +
 "bh=" + hashout + "; " +     
 "h=From:To:Subject:Content-Type:Content-Transfer-Encoding; " +
 "b="; 

string canonicalizedHeaders =
"from:" + from + Environment.NewLine +
"to:" + to + Environment.NewLine +
"subject:" + subject + Environment.NewLine +
@"content-type:text/plain; charset=us-ascii
content-transfer-encoding:quoted-printable
dkim-signature:" + signatureHeader;

TextReader reader = new StringReader(base64privatekey);
Org.BouncyCastle.OpenSsl.PemReader r = new Org.BouncyCastle.OpenSsl.PemReader(reader);
AsymmetricCipherKeyPair o = r.ReadObject() as AsymmetricCipherKeyPair;
byte[] plaintext = Encoding.ASCII.GetBytes(canonicalizedHeaders);
ISigner sig = SignerUtilities.GetSigner("SHA256WithRSAEncryption");
sig.Init(true, o.Private);
sig.BlockUpdate(plaintext, 0, plaintext.Length);
byte[] signature = sig.GenerateSignature();
signatureHeader += Convert.ToBase64String(signature);

MailMessage message = new MailMessage();
message.From = new MailAddress(from);
message.To.Add(new MailAddress(to));
message.Subject = subject;
message.Body = body;

message.Headers.Add("DKIM-Signature", signatureHeader);
SmtpClient client = new SmtpClient(smtp);
client.Send(message);
Console.Write("sent to: " + to);

An email sent to gmail gets the cool “signed-by” info:

{ 14 } Comments

  1. Anonymous | September 20, 2009 at 1:59 pm | Permalink

    Thank you Thank you Thank you very much…

  2. Nimesh | October 6, 2009 at 9:55 am | Permalink

    Hi,

    I am a not technical person and was searching whether it is possible to send a DKIM signed mail using MTA running on Windows platform? Someone suggested it can only be send using Unix platform.

    Since I saw your post saying send using C#, just thought to check with you.

    Request you to reply me at nimesh12345@gmail.com at your earliest.

    Thanks
    Nimesh

  3. Jorge Martin Rodriguez Castro | October 27, 2009 at 2:53 pm | Permalink

    Your article is very interesting. I applied it and all works fine! Please, can you help me about how sign a email using DomainKeys? (because DKIM<>DomainKeys). I have some days trying, but without success
    Thanks. My email is jorgerodcas@hotmail.com

  4. Alistair | November 6, 2009 at 2:38 pm | Permalink

    Hi thanks for the research on this.

    We eventually decided the easiest option was to install hMailServer (free) which has a very simple GUI for setting up DKIM.

    We have it listen on a non-standard port (so that IIS SMTP still works) and use the .Net SmtpClient to create the message in C#.

    Hopefully this info might be useful to Windows admins. Certainly cheaper than the commercial option.

  5. Russ | November 6, 2009 at 8:53 pm | Permalink

    Hi Alistair, I've been meaning to write a quick article on setting up hMailServer. The details for setting up DKIM on hMailServer are here: http://www.hmailserver.com/documentation/latest/?page=reference_domain

    Russ

  6. Anonymous | February 21, 2010 at 7:34 am | Permalink

    Hi Russ,

    Thanks for this fantastic blog post! I had been trying to use a commercial DKIM library to sign my emails and it wasn't working. This post helped me get there. 4+ days of efforts got me nowhere. This article got me there in just a few hours.

    Thanks again,
    Amol.

  7. Anonymous | February 21, 2010 at 6:14 pm | Permalink

    Great !!!
    Thank you very very very much 🙂

  8. EricP | November 12, 2010 at 1:07 pm | Permalink

    Hi,

    The code works for emails with very simple text in the body. If I pass HTML or foreign characters – DKIM doesn’t pass.

    I tried ” Nice Clean C# Generate Quoted-Printable”, but it doesn’t even convert New line correctly (as you do in the hack).

    Do you have suggestions on how to reliably generate “Quoted-Printable” body that will match the one generated by .NET smtp?

    Regards…

  9. russ | November 16, 2010 at 11:32 pm | Permalink

    Unfortunately not, maybe there’s a way to look at the private properties on the .NET class – or use reflector to find the routine they are using to create the quoted-printable? Both not really great solutions. Is it an option for you to use something like hMailServer instead?

  10. Simon Pallister | March 28, 2012 at 2:08 am | Permalink

    This is a great article. I do have one question – what if the To & From have a display name as well as an email address? How should that be dealt with?

  11. russ | March 29, 2012 at 12:42 am | Permalink

    Good question.. The rules on canonicalization are in the RFC here: http://tools.ietf.org/html/rfc4871#page-13. I’d assume you’d need to treat the contents of the To/From header like any other header when you prepare it for signing.

  12. Simon Pallister | March 29, 2012 at 1:23 am | Permalink

    I actually worked it out for myself – if you use email address in the format:

    “Display Name”

    it works just fine 🙂

  13. Alex | July 13, 2014 at 9:21 am | Permalink

    On vb.net r.ReadObject returns nothing
    Is there any workaround solution for vb.net ?

    Dim o As AsymmetricCipherKeyPair = CType(r.ReadObject(), AsymmetricCipherKeyPair) or similar goes for o = nothing

  14. Alex | July 13, 2014 at 9:48 am | Permalink

    Solved
    Dim base64privatekey As String need to have the strings sepparated by lines so r.readobject can find the line —–END RSA PRIVATE KEY—–

    So like

    Dim base64privatekey As String = “—–BEGIN RSA PRIVATE KEY—–” & vbNewLine & _
    “MIICXgIBAAKBgQDAZ8IceJMid3cRk8uODa8W2RwZcgJvn27TEQ97F” & vbNewLine & _
    “em3eLE/uhKfLFJkxma/M1wckZ9jGIsig2esxrKhpwoEZ9qanESJ” & vbNewLine & _
    “GdL4hZRdsn2NZxdcMkQJCtcT1PFswT2ybGMauTaG4lhquzAsQIDAQAB” & vbNewLine & _
    “AoGBALw1Arjs7Sg66fsEsoobMplL+OOfFWun52E8gc2PiMUT0tIIuoGW1” & vbNewLine & _
    “15icZpoRNtlNwYvIFVEDd3N4l0mFb0A32xLt5vWGgARuktSThnYRfEuM” & vbNewLine & _
    “e7LS+j9r3tcd1Ho8KLD9R68NCKUc8KtsYiX2dFQ0183QwkThAkGxHc1K” & vbNewLine & _
    “vTZjuE1KXOdQSyD2wZlsm3xCWeDEow7sAHSJXwXl42w07em2mZtf7ahL” & vbNewLine & _
    “luAtWbTh+QJBAMx7HgMalDiiYo+1A75eqBoA3TTvZ8vGxyNnV5” & vbNewLine & _
    “PPzcmXFIy3zPiWCx5uLZupYofxrgSVQqQi0Crb8lB” & vbNewLine & _
    “J4n4XJOvTTlYd3uB02Ksz3qZQz2Wi2ECQQCK” & vbNewLine & _
    “ympFiw3eHwfcMeG996NQD” & vbNewLine & _
    “KN1hTou” & vbNewLine & _
    “KNuIa5977YcJ+K8BpCeKm73Ak5b7JgtNxNSalvaeHDTEKg==” & vbNewLine & _
    “—–END RSA PRIVATE KEY—–”

    Then that error will be resolved

Post a Comment

Your email is never published nor shared. Required fields are marked *