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
Thank you Thank you Thank you very much…
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
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
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.
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
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.
Great !!!
Thank you very very very much 🙂
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…
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?
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?
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.
I actually worked it out for myself – if you use email address in the format:
“Display Name”
it works just fine 🙂
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
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