Sending Encrypted E-Mails in C#

So I was faced with the problem of sending an encrypted email to a group of people.  Not really thinking, I responded with sure we can do that no problem. Getting back to my desk I started working on the project to find out that it turns out to be more difficult then I had thought.

Doing some internet searches, I found several commercial products that allow you to encrypt emails.  I also found several discussions where people got close to what they were after, most were just after digital signatures without attachments.  Nothing close to a full package.

After a few hours worth of digging I finally was able to come up with a solution that met my needs, multiple addressees and multiple attachments.  Below is the code, note that you need to add a reference to System.Security in your Visual Studio Project to compile this code.

 

 using System;
 using System.Text;
 using System.Net.Mail;
 using System.IO;
 using System.Security.Cryptography.Pkcs;
 using System.Security.Cryptography.X509Certificates;
  
 namespace CommonUtilities
 {
     //requires reference to System.Security
     class EmailUtil
     {
         public static void SendEncryptedEmail(string[] to, string from, string subject, string body, string[] attachments)
         {
             MailMessage message = new MailMessage();
             message.From = new MailAddress(from);
             message.Subject = subject;
  
             if (attachments != null && attachments.Length > 0)
             {
                 StringBuilder buffer = new StringBuilder();
                 buffer.Append("MIME-Version: 1.0\r\n");
                 buffer.Append("Content-Type: multipart/mixed; boundary=unique-boundary-1\r\n");
                 buffer.Append("\r\n");
                 buffer.Append("This is a multi-part message in MIME format.\r\n");
                 buffer.Append("--unique-boundary-1\r\n");
                 buffer.Append("Content-Type: text/plain\r\n");  //could use text/html as well here if you want a html message
                 buffer.Append("Content-Transfer-Encoding: 7Bit\r\n\r\n");
                 buffer.Append(body);
                 if (!body.EndsWith("\r\n"))
                     buffer.Append("\r\n");
                 buffer.Append("\r\n\r\n");
  
                 foreach (string filename in attachments)
                 {
                     FileInfo fileInfo = new FileInfo(filename);
                     buffer.Append("--unique-boundary-1\r\n");
                     buffer.Append("Content-Type: application/octet-stream; file=" + fileInfo.Name + "\r\n");
                     buffer.Append("Content-Transfer-Encoding: base64\r\n");
                     buffer.Append("Content-Disposition: attachment; filename=" + fileInfo.Name + "\r\n");
                     buffer.Append("\r\n");
                     byte[] binaryData = File.ReadAllBytes(filename);
  
                     string base64Value = Convert.ToBase64String(binaryData, 0, binaryData.Length);
                     int position = 0;
                     while (position < base64Value.Length)
                     {
                         int chunkSize = 100;
                         if (base64Value.Length - (position + chunkSize) < 0)
                             chunkSize = base64Value.Length - position;
                         buffer.Append(base64Value.Substring(position, chunkSize));
                         buffer.Append("\r\n");
                         position += chunkSize;
                     }
                     buffer.Append("\r\n");
                 }
  
                 body = buffer.ToString();
             }
             else
             {
                 body = "Content-Type: text/plain\r\nContent-Transfer-Encoding: 7Bit\r\n\r\n" + body;
             }
  
             byte[] messageData = Encoding.ASCII.GetBytes(body);
             ContentInfo content = new ContentInfo(messageData);
             EnvelopedCms envelopedCms = new EnvelopedCms(content);
             CmsRecipientCollection toCollection = new CmsRecipientCollection();
             foreach (string address in to)
             {
                 message.To.Add(new MailAddress(address));
                 X509Certificate2 certificate = null; //Need to load from store or from file the client's cert
                 CmsRecipient recipient = new CmsRecipient(SubjectIdentifierType.SubjectKeyIdentifier, certificate);
                 toCollection.Add(recipient);
             }
  
             envelopedCms.Encrypt(toCollection);
             byte[] encryptedBytes = envelopedCms.Encode();
  
             //add digital signature:
             SignedCms signedCms = new SignedCms(new ContentInfo(encryptedBytes));
             X509Certificate2 signerCertificate = null; //Need to load from store or from file the signer's cert
             CmsSigner signer = new CmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, signerCertificate);
             signedCms.ComputeSignature(signer);
             encryptedBytes = signedCms.Encode();
             //end digital signature section
  
             MemoryStream stream = new MemoryStream(encryptedBytes);
             AlternateView view = new AlternateView(stream, "application/pkcs7-mime; smime-type=signed-data;name=smime.p7m");
             message.AlternateViews.Add(view);
  
             SmtpClient client = new SmtpClient("your.smtp.mailhost");
             //add authentication info if required by your smtp server etc...
             //client.Credentials = CredentialCache.DefaultCredentials;
             client.Send(message);
         }
     }
 }
  

This should get you pretty much everything you need. The only thing left is to load the certificates from somewhere.  I used a function to load them from the current users certificate store on the machine.  I have a previous blog posting on how to load the certificates from the store.  The trick comes in to fetching the certificates for unknown parties etc.