« Home | Verifying a DKIM signature in C# » | Windows Azure - SlashView » | Windows Azure - Getting Started » | Silverlight 2: MVVM, Databinding & Cascading Combo... » | Silverlight 2: MVVM, Databinding & Cascading Combo... » | Google App Engine - Tetris Challenge » | IIS, SSL and Host-Headers » | Amazon EC2 - Now with Windows Server 2003! » | Playing with JQuery and ASP.NET MVC » | Converting Timezones in .net » 

Sunday, September 20, 2009 

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:

Labels: ,

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 :)

Thank you very much... it still work very good

Thank you! This was very helpful

Good job, thank you.

i am getting error at cipher encryption time.
Org.BouncyCastle.OpenSsl.PemReader r is null, hence nothing ahead is working.
plz help

i am also getting same error.

Org.BouncyCastle.OpenSsl.PemReader o is null

plz someone reply

nav / Manpreet - do you have a genuine base64 private key in base64privatekey?

yes it is a valid key. i generated it online using http://dkimcore.org/tools/

and also used all the namespace references.

using System.Text;
using System.Net.Mail;
using Org.BouncyCastle.Crypto;
using System.IO;
using System.Security.Cryptography;
using Org.BouncyCastle.Security;

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;

Hi Nav,
Do you mind emailing me the key you are using? And any code you've got going. This article is pretty old- there's a good chance the Bouncy Castle stuff has changed since I last looked at it.

Email: russ@tinisles.com

Russ

This comment has been removed by the author.

Hi,
thanks for this code.
but I have a problem with gmail, it reports all my emails as dkim neutral (bad format) and I just can't pinpoint the reason for that.

Does anybody have any clue?

lol... I have dived deep into this subject for more than 2 hours. got it working ALMOST.

however, please note, THIS worked only for emails which did not contains any special characters (spaces, new lines etc). THe replace new line with quated-encoding thing DID not work. The sample code provided did not even for for mentioned new-line case. It produced invalid results.

I have found another dedicated encoded on the web for the purpose of encoding whole string to that encoding => did not work either.

Seems like that hack does not work anymore?

Used GMAIL acc to verify results integrity.

Then.. suddenty I read in the comments about hMailServer which I had installed already...
LOL

couple of clicks... whole new code to WIPE OUT just to be restored to what it has been all time long. thank you all

Post a Comment