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:

Verifying a DKIM signature in C#

Quick background: DomainKeys Identified Mail (DKIM) is a digital signature and hash which are placed on your emails to verify they are coming from the domain they claim to be. One step in the defence against SPAM and phishing emails.

What does the signature look like. Here’s a signature grabbed from an email sent from Yahoo mail:

DKIM-Signature: 
 v=1; 
 a=rsa-sha256; 
 c=relaxed/relaxed; 
 d=yahoo.com.au; 
 s=s1024; 
 t=1250214285; 
 bh=cHSvmWBBhlnc7HYJ0cXdBZfzQDwcJskaGayavbFvzSY=; 
 h=Message-ID:X-YMail-OSG:Received:X-Mailer:Date:From:Subject:To:MIME-Version:Content-Type; 
 b=gsz2P/9h3SL8PwBIU8g9dMGnHI+mwfSqjDhrkJljG3HNCbL0I4/DZJBGHnuqRwgpP4Rq0K3E/1z2Zwez4OhMI+naVzGg0P0ePG75GOwfXvl0XBvN8x8j1pgIOS8KQS24E1+6RogWIKJR2mht+KwiFK5uq3BYc9+nC9XMWsIE1fw=

Verifying a signature is going to require a public key. The public key is grabbed from a TXT record on the sender’s the domain. The domain name is built with the “s” and “d” tags above. “s” + “._domainkey.” + d = “s1024._domainkey.yahoo.com.au”.

Using nslookup from the command prompt give us:

C:\>nslookup
Default Server:  DD-WRT
Address:  192.168.1.1

> set type=TXT
> s1024._domainkey.yahoo.com.au
Server:  DD-WRT
Address:  192.168.1.1

Non-authoritative answer:
s1024._domainkey.yahoo.com.au   text =

        "k=rsa; t=y; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4Pel
eB3gfm"
        "JiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bT/xhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj/+XcwIDAQAB; n=A 1024 bit key
;"

The “b=” tag in DKIM-Signature is a signature of the canonicalized headers. The canonicalization process is done to make sure the signature is resilient to header modifications by mail servers. In this example we are using the ‘relaxed’ canonicalization algorithm. (Much more details about canonicalization in the RFC4871).

The original headers:

Message-ID: <88014.32082.qm@web112514.mail.gq1.yahoo.com>
X-YMail-OSG: aJFcCYUVM1mfioaf5zF7Pv_aQQ27DjD57ShG_QdRO6jTz5SGM4lVBkiJ1SD0tZmtvLEHhR4YtNFpTpI26b3VFERE44rlTAN3nP7a9.snzPvxcIIVOy6d0XWg1fvWS5bpOInMbv7M0bltJc6ml3gWPn8S3tT6U.Wz4OXu22meLo5VrikBRKeSGItA5Kw5Asn20GT84javGzv9_3znhG0HMejyHxpDjDbn.C_9OpKMVheG6HmBpgFVYmsW9q2IKc4r08YZvyADKNKnjN066fNhCNaYKWu3PEpHpbV4pZf66cFChegx94phIMQCI0cScc8ZQ5BLRj7qwV2F85C2ib22DWbZvTY-
Received: from [114.76.223.211] by web112514.mail.gq1.yahoo.com via HTTP; Thu, 13 Aug 2009 18:44:43 PDT
X-Mailer: YahooMailRC/1358.27 YahooMailWebService/0.7.338.2
Date: Thu, 13 Aug 2009 18:44:43 -0700 (PDT)
From: Russell Sayers 
Subject: from yahoo
To: diagnostic@smtp2web.com
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="0-1193717202-1250214283=:32082"

After relaxed canonicalization:

message-id:<88014.32082.qm@web112514.mail.gq1.yahoo.com>
x-ymail-osg:aJFcCYUVM1mfioaf5zF7Pv_aQQ27DjD57ShG_QdRO6jTz5SGM4lVBkiJ1SD0tZmtvLEHhR4YtNFpTpI26b3VFERE44rlTAN3nP7a9.snzPvxcIIVOy6d0XWg1fvWS5bpOInMbv7M0bltJc6ml3gWPn8S3tT6U.Wz4OXu22meLo5VrikBRKeSGItA5Kw5Asn20GT84javGzv9_3znhG0HMejyHxpDjDbn.C_9OpKMVheG6HmBpgFVYmsW9q2IKc4r08YZvyADKNKnjN066fNhCNaYKWu3PEpHpbV4pZf66cFChegx94phIMQCI0cScc8ZQ5BLRj7qwV2F85C2ib22DWbZvTY-
received:from [114.76.223.211] by web112514.mail.gq1.yahoo.com via HTTP; Thu, 13 Aug 2009 18:44:43 PDT
x-mailer:YahooMailRC/1358.27 YahooMailWebService/0.7.338.2
date:Thu, 13 Aug 2009 18:44:43 -0700 (PDT)
from:Russell Sayers 
subject:from yahoo
to:diagnostic@smtp2web.com
mime-version:1.0
content-type:multipart/alternative; boundary="0-1193717202-1250214283=:32082"

Now the canonicalized signature, minus the “b=” value. is placed after the headers. This had me scratching my head for a bit until I read this from the RFC:

The header field MUST be presented to the hash algorithm after the body of the message rather than with the rest of the header fields and MUST be canonicalized as specified in the “c=” (canonicalization) tag. The DKIM-Signature header field MUST NOT be included in its own h= tag..

We now have everything we need to verify the signature on the email using Bouncy Castle Crypto APIs


string base64pubkey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfmJiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bT/xhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj/+XcwIDAQAB";
RsaKeyParameters pubKey = PublicKeyFactory.CreateKey(Convert.FromBase64String(base64pubkey)) as RsaKeyParameters;
byte[] signature = Convert.FromBase64String("gsz2P/9h3SL8PwBIU8g9dMGnHI+mwfSqjDhrkJljG3HNCbL0I4/DZJBGHnuqRwgpP4Rq0K3E/1z2Zwez4OhMI+naVzGg0P0ePG75GOwfXvl0XBvN8x8j1pgIOS8KQS24E1+6RogWIKJR2mht+KwiFK5uq3BYc9+nC9XMWsIE1fw=");

string canonicalized =
@"message-id:<88014.32082.qm@web112514.mail.gq1.yahoo.com>
x-ymail-osg:aJFcCYUVM1mfioaf5zF7Pv_aQQ27DjD57ShG_QdRO6jTz5SGM4lVBkiJ1SD0tZmtvLEHhR4YtNFpTpI26b3VFERE44rlTAN3nP7a9.snzPvxcIIVOy6d0XWg1fvWS5bpOInMbv7M0bltJc6ml3gWPn8S3tT6U.Wz4OXu22meLo5VrikBRKeSGItA5Kw5Asn20GT84javGzv9_3znhG0HMejyHxpDjDbn.C_9OpKMVheG6HmBpgFVYmsW9q2IKc4r08YZvyADKNKnjN066fNhCNaYKWu3PEpHpbV4pZf66cFChegx94phIMQCI0cScc8ZQ5BLRj7qwV2F85C2ib22DWbZvTY-
received:from [114.76.223.211] by web112514.mail.gq1.yahoo.com via HTTP; Thu, 13 Aug 2009 18:44:43 PDT
x-mailer:YahooMailRC/1358.27 YahooMailWebService/0.7.338.2
date:Thu, 13 Aug 2009 18:44:43 -0700 (PDT)
from:Russell Sayers &lt;russell_sayers@yahoo.com.au&gt;
subject:from yahoo
to:diagnostic@smtp2web.com
mime-version:1.0
content-type:multipart/alternative; boundary=""0-1193717202-1250214283=:32082""
dkim-signature:v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.com.au; s=s1024; t=1250214285; bh=cHSvmWBBhlnc7HYJ0cXdBZfzQDwcJskaGayavbFvzSY=; h=Message-ID:X-YMail-OSG:Received:X-Mailer:Date:From:Subject:To:MIME-Version:Content-Type; b=";
byte[] message = Encoding.ASCII.GetBytes(canonicalized);


ISigner sig = SignerUtilities.GetSigner("SHA256WithRSAEncryption");
sig.Init(false, pubKey);
sig.BlockUpdate(message, 0, message.Length);
if (sig.VerifySignature(signature))
{
    Console.WriteLine("all good!");
}

Windows Azure – SlashView

Update (18-Sept-2009): Hot off twitter: Introducing the Windows Azure Service Management API – my wish (below) has been granted. ๐Ÿ™‚

Here’s my first attempt playing around with Azure: SlashView

The idea behind the site is pretty simple – I’ve got a worker process scraping all the comments off Slashdot every 10 minutes. I get all the stats off the comments, and display the results in a Silverlight graph.

The learning curve for Azure isn’t too steep if you are already an ASP.NET developer. The only new technology I’ve had come up to speed with is the deployment process, and the table/blog storage services.

Some indispensable resources I’ve discovered along the way:

Wish list: an automated deploy. It can take a long while to ‘Suspend’, ‘Delete’, ‘Deploy’ from the developer portal..

Windows Azure – Getting Started

Windows Azure – so where do you get started? Some links:

I’ve done articles on Amazon’s EC2 and Google’s AppEngine – so I guess I’m obligated to do an Azure article, right? ๐Ÿ™‚

I do have an Azure idea in the works. It’s not very exciting to write an app that is barely different to something I could do on my cheap ASP.NET hosting. Something that cheap hosting doesn’t offer is an easy way to write a background processing application such as the Azure worker role. So I’ve revived a ‘shelved’ screen scraping application. The idea is to pull down all the comments on Slashdot articles, and generate some visualization of the comments growing over time.