S/MIME encryption

S/MIME encrypted data is actually a CMS/PKCS#7 Enveloped data but formatted as MIME (Multipurpose Internet Mail Extensions). OpenSSL creates S/MIME encrypted data with the smime command.

For S/MIME encrypting we need the X.509 certificate of the recipient(s). For decrypting we need a private key corresponding to a certificate used for the encryption.

The default symmetric cipher used for the session key is TrippleDES (Des3) – same as with OpenSSL. This can be specified as method call parameter.

This tutorial will illustrate how to create S/MIME data compatible with OpenSSL using DidiSoft OpenSSL Library for .NET and the DidiSoft.OpenSsl.Cms.OpenSslSmime class

Table of contents

Encrypt file

In order to encrypt a file in S/MIME format we need the X.509 certificate of the recipient. The equivalent OpenSSL command is:

openssl smime -encrypt -des3 -in input.txt -out output.p7m recipient.crt

In the example code below the preferred symmetric cipher for encrypting the data is also specified:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
using System;
using DidiSoft.OpenSsl.Cms;
 
public class EncryptSmimeFile
{
 public static void Demo()
 {
  OpenSslSmime smime = new OpenSslSmime();
  CmsCipherAlgorithm encAlgorithm = CmsCipherAlgorithm.Des3;
  smime.EncryptFile(@"input.txt", @"recipient.crt", @"output.p7m", encAlgorithm);
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
Imports System
Imports DidiSoft.OpenSsl.Cms
 
Public Class EncryptSmimeFile
 Public Shared Sub Demo()
  Dim smime As New OpenSslSmime()
  Dim encAlgorithm As CmsCipherAlgorithm = CmsCipherAlgorithm.Des3
  smime.EncryptFile("input.txt", "recipient.crt", "output.p7m", encAlgorithm)
 End Sub
End Class

Encrypt string

When S/MIME encrypting a string message the output is returned as a result of the method call:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using DidiSoft.OpenSsl.Cms;
 
public class EncryptSmimeString
{
 public void Demo()
 {
  OpenSslSmime smime = new OpenSslSmime();
 
  string certificate = @"Data\public.crt";
 
  string messageToBeEncrypted = "Hello World";
 
  CmsCipherAlgorithm encAlgorithm = CmsCipherAlgorithm.Blowfish;
  string smimeData = smime.EncryptString(messageToBeEncrypted, certificate, encAlgorithm);
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Imports System
Imports DidiSoft.OpenSsl.Cms
 
Public Class EncryptSmimeString
 Public Sub Demo()
  Dim smime As New OpenSslSmime()
 
  Dim certificate As String = "Data\public.crt"
 
  Dim messageToBeEncrypted As String = "Hello World"
 
  Dim encAlgorithm As CmsCipherAlgorithm = CmsCipherAlgorithm.Blowfish
  Dim smimeData As String = smime.EncryptString(messageToBeEncrypted, certificate, encAlgorithm)
 End Sub
End Class

Encrypt Stream

The OpenSslSmime.EncryptStream method will output the encrypted data into an output Stream, but the stream will be left open. This is intentional, for cases when a MemoryStream is passed as output Stream and the invoking code needs to read it afterwards:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using System.IO;
using DidiSoft.OpenSsl.Cms;
 
public class EncryptSmimeStream
{
 public static void Demo()
 {
  OpenSslSmime smime = new OpenSslSmime();
  CmsCipherAlgorithm encAlgorithm = CmsCipherAlgorithm.Aes256;
  MemoryStream mem = new MemoryStream();
  using (Stream inputStream = File.OpenRead(@"input.txt"))
  {
	smime.EncryptStream(inputStream, @"public.crt", mem, encAlgorithm);
  }
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Imports System
Imports System.IO
Imports DidiSoft.OpenSsl.Cms
 
Public Class EncryptSmimeStream
 Public Shared Sub Demo()
  Dim smime As New OpenSslSmime()
  Dim encAlgorithm As CmsCipherAlgorithm = CmsCipherAlgorithm.Aes256
  Dim mem As New MemoryStream()
  Using inputStream As Stream = File.OpenRead("Data\Input.txt")
	smime.EncryptStream(inputStream, "Data\public.crt", mem, encAlgorithm)
  End Using
 End Sub
End Class

Encrypt bytes

Byte array encryption is no different than encrypting file or string. The result encrypted S/MIME data is returned as byte array:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using DidiSoft.OpenSsl.Cms;
 
public class EncryptSmimeBytes
{
 public void Demo()
 {
  OpenSslSmime smime = new OpenSslSmime();
 
  string certificate = @"Data\public.crt";
 
  byte[] dataToBeEncrypted = new byte[] { 1, 2, 3 };
 
  CmsCipherAlgorithm encAlgorithm = CmsCipherAlgorithm.Blowfish;
  byte[] smimeData = smime.EncryptBytes(dataToBeEncrypted, certificate, encAlgorithm);
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Imports System
Imports DidiSoft.OpenSsl
Imports DidiSoft.OpenSsl.Cms
 
Public Class EncryptSmimeBytes
 Public Sub Demo()
  Dim smime As New OpenSslSmime()
 
  Dim certificate As String = "Data\public.crt"
 
  Dim dataToBeEncrypted As Byte() = New Byte() {1, 2, 3}
 
  Dim encAlgorithm As CmsCipherAlgorithm = CmsCipherAlgorithm.Blowfish
  Dim smimeData As Byte() = smime.EncryptBytes(dataToBeEncrypted, certificate, encAlgorithm)
 End Sub
End Class

Encrypting with X509Certificate2 instance

Overloaded Encrypt methods exist that accept an encryption certificate directly from a System.Security.Criptography.X509Certificates.X509Certificate2 instance as you can see from the example below:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using System.Security.Cryptography.X509Certificates;
using DidiSoft.OpenSsl.Cms;
 
public class EncryptSmimeWithX509Certificate2
{
 public static void Demo()
 {
  X509Certificate2 cert2 = new X509Certificate2(@"C:\Projects\didisoft.pkcs12.p12", "cobra34", X509KeyStorageFlags.Exportable);
 
  OpenSslSmime smime = new OpenSslSmime();
  CmsCipherAlgorithm encAlgorithm = CmsCipherAlgorithm.Des3;
  smime.EncryptFile(@"input.txt", cert2, @"smime.p7m", encAlgorithm);
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
12
13
Imports System
Imports System.Security.Cryptography.X509Certificates
Imports DidiSoft.OpenSsl.Cms
 
Public Class EncryptSmimeWithX509Certificate2
 Public Shared Sub Demo()
  Dim cert2 As New X509Certificate2("C:\Projects\didisoft.pkcs12.p12", "cobra34", X509KeyStorageFlags.Exportable)
 
  Dim smime As New OpenSslSmime()
  Dim encAlgorithm As CmsCipherAlgorithm = CmsCipherAlgorithm.Des3
  cms.EncryptFile("input.txt", cert2, "smime.p7m", encAlgorithm)
 End Sub
End Class

Encrypting for multiple recipients

An encrypted S/MIME message can be created for multiple recipients. In that case we must use the overloaded Encrypt methods that expect an array of X.509 certificates :

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using DidiSoft.OpenSsl.X509;
using DidiSoft.OpenSsl.Cms;
 
public class EncryptSmimeMultipleRcpt
{
 public void Demo()
 {
  Certificate cert1 = Certificate.Load("recipient1.crt");
  Certificate cert2 = Certificate.Load("recipient2.crt");
 
  OpenSslSmime smime = new OpenSslSmime();
 
  Certificate[] recipients = new Certificate[] { cert1, cert2 };
 
  smime.EncryptFile(@"input.txt", recipients, @"smime.p7m");
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Imports System
Imports DidiSoft.OpenSsl.X509
Imports DidiSoft.OpenSsl.Cms
 
Public Class EncryptSmimeMultipleRcpt
 Public Sub Demo()
  Dim cert1 As Certificate = Certificate.Load("recipient1.crt")
  Dim cert2 As Certificate = Certificate.Load("recipient2.crt")
 
  Dim smime As New OpenSslSmime()
 
  Dim recipients As Certificate() = New Certificate() {cert1, cert2}
 
  smime.EncryptFile("input.txt", recipients, "smime.p7m")
 End Sub
End Class

Decrypt file

In order to decrypt S/MIME encrypted message we must poses a private key corresponding to a certificate used for the encryption. OpenSSL Library for .NET will also detect automatically the symmetric cipher used so there is no need to specify it:

C# example

1
2
3
4
5
6
7
8
9
10
11
using System;
using DidiSoft.OpenSsl.Cms;
 
public class DecryptSmimeFile
{
 public static void Demo()
 {
  OpenSslSmime smime = new OpenSslSmime();
  cms.DecryptFile(@"smime.p7m", @"private_key.pem", @"Output.txt");
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
Imports System
Imports DidiSoft.OpenSsl.Cms
 
Public Class DecryptSmimeFile
 Public Shared Sub Demo()
  Dim smime As New OpenSslSmime()
  smime.DecryptFile("smime.p7m", "private_key.pem", "Output.txt")
 End Sub
End Class

Decrypt S/MIME string

The OpenSslSmime.DecryptString is similar to the DecryptFile illustrated above:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using DidiSoft.OpenSsl.Cms;
 
public class DecryptSmimeString
{
 public void Demo(string smimeEncryptedData)
 {
  OpenSslSmime smime = new OpenSslSmime();
 
  string decryptedMessage = smime.DecryptString(smimeEncryptedData, "private_key.pem");
  Console.WriteLine(decryptedMessage);
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
Imports System
Imports DidiSoft.OpenSsl.Cms
 
Public Class DecryptSmimeString
 Public Sub Demo(smimeEncryptedData As String)
  Dim cms As New OpenSslSmime()
 
  Dim decryptedMessage As String = smime.DecryptString(smimeEncrypted, "private_key.pem")
  Console.WriteLine(decryptedMessage)
 End Sub
End Class

Decrypt Stream

The OpenSslSmime.DecryptStream methods just like the Stream encryption one will leave the input and output Streams open! The invoking code block has the obligation to close them:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;
using System.IO;
using DidiSoft.OpenSsl.Cms;
 
public class DecryptSmimeStream
{
 public static void Demo()
 {
  OpenSslSmime smime = new OpenSslSmime();
  MemoryStream mem = new MemoryStream();
  using (Stream inputStream = File.OpenRead(@"smime.p7m"))
  {
	smime.DecryptStream(inputStream, @"Data\private_key.pem", mem);
  }
  // mem can be read
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Imports System
Imports System.IO
Imports DidiSoft.OpenSsl.Cms
 
Public Class DecryptCmsStream
 Public Shared Sub Demo()
   Dim smime As New OpenSslSmime()
 
   Dim mem As New MemoryStream()
   Using inputStream As Stream = File.OpenRead("smime.p7m")
     cms.DecryptStream(inputStream, "Data\private_key.pem", mem)
   End Using
   ' mem can be read
 End Sub
End Class

Decrypt bytes

Decrypted byte arrays obtained from S/MIME encrypted data will produce the raw content as byte array:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using DidiSoft.OpenSsl.Cms;
 
public class DecryptSmimeBytes
{
 public void Demo(byte[] smimeData)
 {
  OpenSslSmime smime = new OpenSslSmime();
 
  string privateKey = @"Data\private_key.pem";
  byte[] decryptedData = smime.DecryptBytes(smimeData, privateKey);
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
12
Imports System
Imports DidiSoft.OpenSsl
Imports DidiSoft.OpenSsl.Cms
 
Public Class DecryptBytes
 Public Sub Demo(smimeData As Byte())
  Dim smime As New OpenSslSmime()
 
  Dim privateKey As String = "Data\private_key.pem"
  Dim decryptedData As Byte() = smime.DecryptBytes(smimeData, privateKey)
 End Sub
End Class

Decrypting with X509Certificate2 instance

If an instance of System.Security.Cryptography.X509Certificates.X509Certificate2 contains a private key it can also be used for decrypting:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System;
using System.Security.Cryptography.X509Certificates;
using DidiSoft.OpenSsl.Cms;
 
public class DecryptSmimeWithX509Certificate2
{
 public static void Demo()
 {
  OpenSslSmime smime = new OpenSslSmime();
  X509Certificate2 cert2 = new X509Certificate2(@"C:\Projects\didisoft.pkcs12.p12", "cobra34", X509KeyStorageFlags.Exportable);
 
  smime.DecryptFile(@"smime.p7m", cert2, @"output.txt");
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
12
Imports System
Imports System.Security.Cryptography.X509Certificates
Imports DidiSoft.OpenSsl.Cms
 
Public Class DecryptSmimeWithX509Certificate2
 Public Shared Sub Demo()
  Dim cert2 As New X509Certificate2("C:\Projects\didisoft.pkcs12.p12", "cobra34", X509KeyStorageFlags.Exportable)
 
  Dim smime As New OpenSslSmime()
  smime.DecryptFile("smime.p7m", cert2, "output.txt")
 End Sub
End Class

Exception handling

All the methods provided from the OpenSslCms class will throw DidiSoft.OpenSsl.OpenSslException in case of cryptography related error.

Specific sub classes can be caught beforehand in order to identify more thoroughly what went wrong and possibly take recovery cations.

All Encrypt methods will throw a DidiSoft.OpenSsl.Exceptions.WrongPublicKeyException if the provided certificate is not a valid X.509 certificate.

1
2
3
4
5
6
7
8
9
10
11
12
13
OpenSslSmime smime = new OpenSslSmime();
try 
{
 smime.Encrypt...
} 
catch (DidiSoft.OpenSsl.Exceptions.WrongPublicKeyException exc)
{
// handle case when the provided certificate is actually not a X509.Certificate
}
catch (DidiSoft.OpenSsl.OpenSslException e)
{
// general OpenSSL error
}

All Decrypt methods will throw a DidiSoft.OpenSsl.Exceptions.WrongPrivateKeyException if the provided private key doesn’t match any certificate used to encrypt the message.

If the input provided for decryption is not S/MIME encrypted Data, then a DidiSoft.OpenSsl.Exceptions.CmsFormatException will be thrown:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
OpenSslSmime smime = new OpenSslSmime();
try 
{
 smime.Decrypt...
} 
catch (DidiSoft.OpenSsl.Exceptions.WrongPrivateKeyException exc)
{
// handle case when the provided private key doesn't match any certificate
}
catch (DidiSoft.OpenSsl.Exceptions.WrongPasswordException exc)
{
// password provided for encrypted private key doesn't match
}
catch (DidiSoft.OpenSsl.Exceptions.CmsFormatException exc)
{
// the provided input is not S/MIME encrypted Data
}
catch (DidiSoft.OpenSsl.OpenSslException e)
{
// general OpenSSL error
}

Summary

This chapter illustrated how to use DidiSoft.OpenSsl.Cms.OpenSslSmime class to encrypt and decrypt S/MIME data.

All the methods demonstrated here will produce content compatible with the popular OpenSSL software and its smime command. The decryption method provided from the OpenSslCms class will automatically determine the symmetric cipher used for encrypted the data in the S/MIME message.