PGP/MIME in .NET

The Email API provided with .NET Framework supports only a limited portion of the MIME (Multi Purpose Mail Extensions). In order to overcome this obstacle and implement PGP/MIME support in DidiSoft OpenPGP Library for .NET we have decided to provide a somehow universal solution that plugs into existing third party Email API’s for .NET.

In this tutorial we are going to introduce the DidiSoft.Pgp.Mail.dll assembly and the usage of the DidiSoft.Pgp.Mail namespace in general and with some of the popular third party solutions for .NET Email API.  Our PGP/MIME solution relies on a third party Mail API for sending/receiving the MIME emails. It can be plugged into any Mail API that provides load/save of MIME emails in byte[] array/stream.

Table of contents
First steps with DidiSoft.Pgp.Mail
Sending emails

Receiving emails

Sample Application

Integrations
Integration with MailKit and MimeKit
Integration with Aspose.Mail
Integration with Rebex.Mail
Integration with OpenPOP library

First steps with DidiSoft.Pgp.Mail

The PGP/MIME support is provided in an additional assembly DLL file : DidiSoft.Pgp.Mail.dll and namespace DidiSoft.Pgp.Mail.  The main class is PGPMailLib which has to be instantiated. The methods that process MIME emails return byte[] arrays with the result serialized MIME email, for example the Encrypt method returns byte[] array with the PGP/MIME encrypted email. The methods of the PGPMailLib class follow a similar usage pattern as the DidiSoft.Pgp.PGPLib class (but instead of files/streams it processes MIME serialized emails) and this tutorial assumes you are already familiar with it.

Below you can see two lines that show how to instantiate PGPMailLib and invoke a method:

DidiSoft.Pgp.Mail.PGPMailLib mailLib = new DidiSoft.Pgp.Mail.PGPMailLib();
byte[] encryptedMessage = mailLib.Encrypt(...);
// now the serialized encryptedMessage shall be used in a third party Email sending API

After handling and producing encrypted/decrypted MIME message, we can use a Load method from the third party Email API we use and proceed with the processing, for example send the encrypted email; show the decrypted message, etc. Check the table of contents for concrete integration examples.

Sending emails

Basically there are three types of PGP/MIME emails: encrypted, signed and both signed and encrypted. Juts like with the DidiSoft.Pgp.PGPLib class the PGP keys needed for encryption/signing can be provided as separate files or ASCII armored inline strings or from a KeyStore container. For the above three operations there are methods with similar names:

PGPMailLib.Encrypt // for PGP/MIME encrypted only emails
PGPMailLib.Sign // for PGP/MIME signed only emails
PGPMailLib.SignAndEncrypt // for PGP/MIME signed and encrypted emails


Lets see how to create an encrypted email both ways.

Encrypting PGP/MIME with public key from the file system

1
2
3
4
5
Stream messageSource = new MemoryStream(serializedEmailFromThirdPartyMailAPI);
string publicKeyPath = @"c:\Keys\recipient_key.asc"; // this can also be an ASCII armored serialized key
DidiSoft.Pgp.Mail.PGPMailLib mailLib = new DidiSoft.Pgp.Mail.PGPMailLib();
byte[] pgpEmail = mailLib.Encrypt(messageSource, publicKeyPath);// now we can Load the result serialized email in a third party mail API and send it.

Note: have you noticed the mysterious serializedEmailFromThirdPartyMailAPI variable above? It comes from a MIME email serialized from a third party Mail API. See the Integrations part in the TOC for concrete examples!

Encrypting PGP/MIME with key from a KeyStore

Now lets see how to do the same when having multiple recipients’ keys stored in a KeyStore class. In that case we can utilize the fact that a key User ID property follows the same pattern used in displaying Email addresses : “Recipient Name <recipient.email@recipient.site>” and specify the desired encryption key by the email of the recipient:

1
2
3
4
5
Stream messageSource = new MemoryStream(serializedEmailFromThirdPartyMailAPI);
DidiSoft.Pgp.KeyStore keyStore = new DidiSoft.Pgp.KeyStore("mykeys.store", "my pass");
DidiSoft.Pgp.Mail.PGPMailLib mailLib = new DidiSoft.Pgp.Mail.PGPMailLib();
byte[] pgpEmail = mailLib.Encrypt(messageSource, keyStore, "recipient.email@recipient.site");// now we can Load the result serialized email in a third party mail API and send it.


Signing PGP/MIME

When creating a PGP/MIME signed email we have to specify the signing private key (our own key) and its password. Again we have the option to use a key from a KeyStore object or from a file (or ASCII armored string):

1
2
3
4
Stream messageSource = new MemoryStream(serializedEmailFromThirdPartyMailAPI);
DidiSoft.Pgp.KeyStore keyStore = new DidiSoft.Pgp.KeyStore("mykeys.store", "my pass");
DidiSoft.Pgp.Mail.PGPMailLib mailLib = new DidiSoft.Pgp.Mail.PGPMailLib();
byte[] pgpEmail = mailLib.Sign(messageSource, keyStore, "my.email@my.site", "my private key password");


Signed and Encrypted PGP/MIME

This produces a PGP/MIME email containing the one pass signed and encrypted source mail message, including the attachments. The parameters are arranged the same way as with the PGPLib.SignAndEncrypt call: first is specified the signing private key along with its password and then the recipient public key for encrypting:

1
2
3
4
Stream messageSource = new MemoryStream(serializedEmailFromThirdPartyMailAPI);
DidiSoft.Pgp.KeyStore keyStore = new DidiSoft.Pgp.KeyStore("mykeys.store", "my pass");
DidiSoft.Pgp.Mail.PGPMailLib mailLib = new DidiSoft.Pgp.Mail.PGPMailLib();
byte[] pgpEmail = mailLib.SignAndEncrypt(messageSource, keyStore, "my.email@my.site", "my private key password", "recipient.email@recipient.site");

 


What about Attachments?

In PGP/MIME mails in contrast to PGP/inline emails the attachments are part of the encrypted source message. so you just have to add the attachments as ordinary files to the source MIME email and then pass the whole message serialized to PGPMailLib. The result encrypted/and or signed/ PGP/MIME will contain the attachments inside.

Receiving emails

Receiving PGP/MIME emails is no different from receiving .pgp files. In order to determine what to do with the email and if we don’t know nothing about it in advance, we have to first perform a few inspection steps:

  • decide what kind of PGP/MIME email is it : encrypted or signed
  • inspect do we have a suitable decryption key (if encrypted) or an appropriate signature verification public key if it is signed
  • and finally perform the actual processing – decrypting or extracting the signed content

The methods offered by the PGPMailLib class work transparently for both PGP/MIME and PGP/inline emails so you can safely use them for both. The only difference is with attachments , which will be explained below.


Decide what kind of PGP email we have?

We can determine the type of email with the methods PGPMailLib.IsEncrypted and PGPMailLib.IsSigned like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Stream messageSource = new MemoryStream(serializedEmailFromThirdPartyMailAPI);
DidiSoft.Pgp.Mail.PGPMailLib mailLib = new DidiSoft.Pgp.Mail.PGPMailLib();
if (mailLib.IsEncrypted(messageStream) || mailLib.IsSigned(messageStream)){
 if (mailLib.IsEncrypted(messageStream))
 {
  // we have PGP encrypted email, can be signed and encrypted as well
 }
 else // mailLib.IsSigned(messageStream)
 {
  // we have PGP signed only email
 }
}
else
{
 // this is not a not a PGP email
}


Determine the decryption key(s)

We can get the raw Key ID(s) of the keys that have encrypted a PGP/MIME email with the method PGPMailLib.ListEncryptionKeyIds. This can help us determine do we have a matching key for decryption. (see the KeyStore topic for details)

1
2
Stream messageStream = ... // Stream obtained from a MIME serialized email
long[] rawKeyIds = mailLib.ListEncryptionKeyIds(messageStream);


Determine the signatures

We can determine who has signed a PGP email with the method PGPMailLib.ListSignatures which returns an array of DidiSoft.Pgp.Inspect.SignatureItem

1
2
Stream messageStream = ... // Stream obtained from a PGP signed only email
DidiSoft.Pgp.Inspect.SignatureItem[] signatures = mailLib.ListSignatures(messageStream);


Decrypt a PGP email

After we know for sure that we have the right decryption key for an email we can proceed by decrypting with a private key from a file or from a KeyStore container. Lets see how to decrypt it with a key in a file:

1
2
3
4
DidiSoft.Pgp.Mail.PGPMailLib mailLib = new DidiSoft.Pgp.Mail.PGPMailLib();
Stream messageStream = ... // Stream obtained from a PGP email
byte[] decryptedContent = mailLib.Decrypt(messageStream, @"c:\my_key.asc", "my key password");// load the decryptedContent in a third party Mail API that supports MIME emails


Verify and extract PGP signed only email

When receiving signed only PGP emails we can extract the content part and verify the signature in two steps. Lets see how to do this with a KeyStore container holding the public keys of our common recipients:

1
2
3
4
5
6
7
8
9
10
DidiSoft.Pgp.Mail.PGPMailLib mailLib = new DidiSoft.Pgp.Mail.PGPMailLib();
Stream messageStream = ... // Stream obtained from a PGP email
DidiSoft.Pgp.KeyStore ks = new DidiSoft.Pgp.KeyStore("mykeys.store", "my password");
DidiSoft.Pgp.SignatureCheckResult signatureCheck = mailLib.Verify(messageStream, ks);if (SignatureCheckResult.SignatureVerified.Equals(signatureCheck))
{
 Console.Write("Signature verified");
}
byte[] decryptedContent = mailLib.ExtractSignedMessage(messageStream);
// load the decryptedContent in a third party Mail API that supports MIME emails


What about attachments in decrypted PGP/inline email

In the case of PGP/MIME encrypted email, if there are attachments, they will be available after decrypting and loading the decrypted bytes into the Mail Library of your choice (see Integrations). But at the beginning of PGP when the PGP/MIME standard didn’t existed attachments were added as PGP encrypted files attached to the email.

You can process those attachments the same way as ordinary PGP encrypted files, just store them to temporary files and decrypt them.

Sample Application

DidiSoft OpenPGP Library for .NET ships with a demo mail application PgpMailApp that illustrates the above functions. The demo app is located in the Examples\PgpMailApp folder and illustrates integration with MailKit.

In order to run the demo app you have to add the MailKit assemblies through NuGet (in Visual Studio, menu Tools/NuGet Package Manager/Package Manager Console)

PM> Install-Package MailKit

Integration with MailKit and MimeKit

MimeKit is a popular open source Mail API for .NET.  Lets see how to create a MimeKit.MailMessage with it, pass it serialized to PGPMailLib then get back the encrypted email :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MimeKit.MimeMessage message = new MimeKit.MimeMessage();
message.From.Add("john.doe@gmail.com");
message.To.Add("patricia.peterson@realmccoy.com");
message.Subject = "Your greatest fan";
 
MimeKit.BodyBuilder builder = new MimeKit.BodyBuilder();
builder.TextBody = "Hi, thank you for all the songs you've made till now.";
message.Body = builder.ToMessageBody();
 
string publicKeyPath = @"c:\Keys\patricia_key.asc";
PGPMailLib mailLib = new PGPMailLib();
 
MemoryStream messageSource = new MemoryStream();
message.WriteTo(messageSource); 
byte[] pgpEmail = mailLib.Encrypt(messageSource, publicKeyPath);
MimeKit.MimeMessage pgpMessageReadyForSending = MimeKit.MimeMessage.Load(new MemoryStream(pgpEmal));
// now we can send pgpMessageReadyForSending with MailKit.Net.Smtp.SmtpClient

Integration with Aspose.Mail

Aspose.Mail is a commonly used commercial Mail API for the .NET Framework. Here we have listed how to serialize a MIME email from Aspose.Email.MailMessage to Stream so it can be used by PGPMailLib and how to create a new instance of Aspose.Email.MailMessage from a byte[] array afterwards.

1
2
3
4
5
6
7
8
Aspose.Email.MailMessage message = MailMessage.Load(dataDir + "test.eml");
 
MemoryStream messageSource = new MemoryStream();
message.Save(messageSource, SaveOptions.DefaultEml);// messageSource can now be passed to PGPMailLib
 
byte[] decryptedEml = new PGPMailLib().Decrypt(messageSource, ...
Aspose.Email.MailMessage decryptedMessage = MailMessage.Load(new MemoryStream(decryptedEml));

Integration with Rebex.Mail

Rebex.Mail has a good MIME parsing support and integrates with no issues. Lets see how to decrypt an email :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Rebex.Mime.MimeMessage incomingMessage = ...
Rebex.Mime.MimeMessage decryptedMessage = new Rebex.Mime.MimeMessage();
 
DidiSoft.Pgp.Mail.PGPMailLib mailLib = new DidiSoft.Pgp.Mail.PGPMailLib();
using (MemoryStream input = new MemoryStream());
{
 incommingMessage.Save(input); byte[] decryptedBytes = mailLib.Decrypt(input.ToArray(), @"C:\Keys\private.key", "key pass");
 using (MemoryStream decryptedStream = new MemoryStream(decryptedBytes))
 {
  decryptedMessage.Load(decryptedStream);
 }
 // decryptedMessage can be displayed in the Rebex.Samples.MessageView form available in their POP3_CS samples.
}

Integration with OpenPOP library

OpenPOP is also a commonly used open source Mail API for .NET. Below you can see how to convert from OpenPop.Mime.Message to Stream and vice versa:

1
2
3
4
5
6
7
8
OpenPop.Mime.Message message = ...
 
MemoryStream messageSource = new MemoryStream();
message.Save(messageSource);// messageSource can now be passed to PGPMailLib
 
byte[] decryptedEml = new PGPMailLib().Decrypt(messageSource, ...
OpenPop.Mime.Message decryptedMessage = OpenPop.Mime.Message.Load(new MemoryStream(decryptedEml));

 

Summary

This chapter introduced an additional helper class designed for dealing with PGP/MIME emails and located in an additional DLL file DidiSoft.Pgp.Mail.dll.

The class was designed for an easy integration with third party Mail API’s for the .NET Framework. It is not a full featured Mail API itself, but rather a pluggable helper for adding PGP/MIME support for existing common Mail API’s.

The class handles transparently incoming PGP/MIME and PGP-inline email messages serialized in Streams and returns byte[] arrays contents of the processed MIME email messages that can be loaded into third party Mail API’s with decent MIME parsers and used from there.