Encrypted Enveloped Data

Encrypted Enveloped Data created according to Cryptographic Message Syntax (CMS)/PKCS#7 consists of the encrypted data and an encrypted session key:

 Enveloped Data
+-------------------------+
| Encrypted data          |
+-------------------------+
| Session key (encrypted) |
+-------------------------+

OpenSSL creates encrypted Enveloped Data with the cms and pkcs7 (now obsolete) commands, but the –outform parameter must also be specified, otherwise S/MIME encrypted output will be produced:

openssl cms -outform pem -in input.txt -out encrypted.pem cert.pem

For encrypted Enveloped Data 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. The same applies to the output format (DER or PEM). When decrypting those parameters can be omitted in contrast to OpenSSL.

This tutorial will illustrate how to create encrypted Enveloped Data compatible with OpenSSL using DidiSoft OpenSSL Library for .NET

Table of contents

Encrypt file

In order to encrypt an Enveloped Data packet we need the X.509 certificate of the recipient. In this example both the symmetric cipher and output format are specified:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using DidiSoft.OpenSsl.Cms;
 
public class EncryptCmsFile
{
 public static void Demo()
 {
  OpenSslCms cms = new OpenSslCms();
  CmsCipherAlgorithm encAlgorithm = CmsCipherAlgorithm.Aes256;
  bool pemFormat = true;
  cms.EncryptFile(@"Data\Input.txt", @"Data\public.crt", @"Data\encrypted.dat", encAlgorithm, pemFormat);
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
Imports System
Imports DidiSoft.OpenSsl.Cms
 
Public Class EncryptCmsFile
 Public Shared Sub Demo()
  Dim cms As New OpenSslCms()
  Dim encAlgorithm As CmsCipherAlgorithm = CmsCipherAlgorithm.Aes256
  Dim pemFormat As Boolean = True
  cms.EncryptFile("Data\Input.txt", "Data\public.crt", "Data\encrypted.dat", encAlgorithm, pemFormat)
 End Sub
End Class

Encrypt string

When CMS encrypting a string message the output is a PEM formatted string. For the example below the symmetric cipher used for encrypting the data is selected to be Blowfish:

C# example

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

VB.NET example

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

Encrypt Stream

The OpenSslCms.EncryptStream method will output the Enveloped Data into an output Stream, but the stream will be left open. This is intentional and the invoking code has the obligation to close the output and input Streams:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using System.IO;
using DidiSoft.OpenSsl.Cms;
 
public class EncryptCmsStream
{
 public static void Demo()
 {
  OpenSslCms cms = new OpenSslCms();
  CmsCipherAlgorithm encAlgorithm = CmsCipherAlgorithm.Aes256;
  bool pemFormat = true;
  MemoryStream mem = new MemoryStream();
  using (Stream inputStream = File.OpenRead(@"Data\Input.txt"))
  {
	cms.EncryptStream(inputStream, @"Data\public.crt", mem, encAlgorithm, pemFormat);
  }
 }
}

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 EncryptCmsStream
 Public Shared Sub Demo()
  Dim cms As New OpenSslCms()
  Dim encAlgorithm As CmsCipherAlgorithm = CmsCipherAlgorithm.Aes256
  Dim pemFormat As Boolean = True
  Dim mem As New MemoryStream()
  Using inputStream As Stream = File.OpenRead("Data\Input.txt")
	cms.EncryptStream(inputStream, "Data\public.crt", mem, encAlgorithm, pemFormat)
  End Using
 End Sub
End Class

Encrypt bytes

Byte array encryption is no different than encrypting file or string. The result encrypted CMS Enveloped Data is returned as byte array. In the example below the symmetric cipher is set to Blowfish for demonstration, please note that by default OpenSSL uses Des3:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System;
using DidiSoft.OpenSsl.Cms;
 
public class EncryptCmsBytes
{
 public void Demo()
 {
  OpenSslCms cms = new OpenSslCms();
 
  string certificate = @"Data\public.crt";
  byte[] dataToBeEncrypted = new byte[] { 1, 2, 3 };
 
  CmsCipherAlgorithm encAlgorithm = CmsCipherAlgorithm.Blowfish;
  byte[] envelopedData = cms.EncryptBytes(dataToBeEncrypted, 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
Imports DidiSoft.OpenSsl.Cms
 
Public Class EncryptCmsBytes
 Public Sub Demo()
  Dim cms As New OpenSslCms()
 
  Dim certificate As String = "Data\public.crt"
  Dim dataToBeEncrypted As Byte() = New Byte() {1, 2, 3}
 
  Dim encAlgorithm As CmsCipherAlgorithm = CmsCipherAlgorithm.Blowfish
  Dim envelopedData As Byte() = cms.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
16
using System;
using System.Security.Cryptography.X509Certificates;
using DidiSoft.OpenSsl.Cms;
 
public class EncryptCmsWithX509Certificate2
{
 public static void Demo()
 {
  X509Certificate2 cert2 = new X509Certificate2(@"C:\Projects\didisoft.pkcs12.p12", "cobra34", X509KeyStorageFlags.Exportable);
 
  OpenSslCms cms = new OpenSslCms();
  CmsCipherAlgorithm encAlgorithm = CmsCipherAlgorithm.Des3;
  bool pemFormat = true;
  cms.EncryptFile(@"Data\Input.txt", cert2, @"Data\enveloped.dat", encAlgorithm, pemFormat);
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Imports System
Imports System.Security.Cryptography.X509Certificates
Imports DidiSoft.OpenSsl.Cms
 
Public Class EncryptCmsWithX509Certificate2
 Public Shared Sub Demo()
  Dim cert2 As New X509Certificate2("C:\Projects\didisoft.pkcs12.p12", "cobra34", X509KeyStorageFlags.Exportable)
 
  Dim cms As New OpenSslCms()
  Dim encAlgorithm As CmsCipherAlgorithm = CmsCipherAlgorithm.Des3
  Dim pemFormat As Boolean = True
  cms.EncryptFile("Data\Input.txt", cert2, "Data\enveloped.dat", encAlgorithm, pemFormat)
 End Sub
End Class

Encrypting for multiple recipients

A CMS encrypted Enveloped Data can be created for more than one recipient. In that case we must use the overloaded Encrypt methods that expect an array of 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 EncryptCmsMultipleRcpt
{
 public void Demo()
 {
  Certificate cert1 = Certificate.Load(@"recipient1.crt");
  Certificate cert2 = Certificate.Load(@"recipient2.crt");
 
  OpenSslCms cms = new OpenSslCms();
 
  Certificate[] recipients = Certificate[] { cert1, cert2 };
 
  cms.EncryptFile(@"Data\Input.txt", recipients, @"Data\enveloped.dat");
 }
}

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 EncryptCmsMultipleRcpt
 Public Sub Demo()
  Dim cert1 As Certificate = Certificate.Load("recipient1.crt");
  Dim cert2 As Certificate = Certificate.Load("recipient2.crt");
 
  Dim cms As New OpenSslCms()
 
  Dim recipients As Certificate() = New Certificate() {cert1, cert2}
 
  cms.EncryptFile("Data\Input.txt", recipients, "Data\enveloped.dat")
End Sub
End Class

Decrypt file

In order to decrypt a CMS/PKCS7 Enveloped Data object we must poses a private key corresponding to a certificate used for encrypting the data. The OpenSSL command line needs some hints in order to be able to decrypt Enevloped Data but with OpenSSL Library for .NET they are not needed – the library automatically determines if the input is PEM or DER encoded and the symmetric algorithm needed for decrypting:

C# example

1
2
3
4
5
6
7
8
9
10
11
using System;
using DidiSoft.OpenSsl.Cms;
 
public class DecryptCmsFile
{
 public static void Demo()
 {
  OpenSslCms cms = new OpenSslCms();
  cms.DecryptFile(@"Data\encrypted.dat", @"Data\private_key.pem", @"Data\Output.txt");
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
Imports System
Imports DidiSoft.OpenSsl.Cms
 
Public Class DecryptCmsFile
 Public Shared Sub Demo()
  Dim cms As New OpenSslCms()
  cms.DecryptFile("Data\encrypted.dat", "Data\private_key.pem", "Data\Output.txt")
 End Sub
End Class

Decrypt string

The OpenSslCms.DecryptString expects the input to be Enveloped Data in PEM format and produces the decrypted output formatted as UTF-8 string:

C# example

1
2
3
4
5
6
7
8
9
10
11
12
13
using System;
using DidiSoft.OpenSsl.Cms;
 
public class DecryptCmsString
{
 public void Demo(string pemEnvelopedData)
 {
  OpenSslCms cms = new OpenSslCms();
 
  string decryptedMessage = cms.DecryptString(pemEnvelopedData, "Data\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 DecryptCmsString
 Public Sub Demo(pemEnvelopedData As String)
  Dim cms As New OpenSslCms()
 
  Dim decryptedMessage As String = cms.DecryptString(pemEnvelopedData, "Data\private_key.pem")
  Console.WriteLine(decryptedMessage)
 End Sub
End Class

Decrypt Stream

The OpenSslCms.DecryptStream methods just like the Stream encryption method will leave the input and output Streams open! Our 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 DecryptCmsStream
{
 public static void Demo()
 {
  OpenSslCms cms = new OpenSslCms();
  MemoryStream mem = new MemoryStream();
  using (Stream inputStream = File.OpenRead(@"Data\encrypted.dat"))
  {
	cms.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 cms As New OpenSslCms()
 
   Dim mem As New MemoryStream()
   Using inputStream As Stream = File.OpenRead("Data\encrypted.dat")
     cms.DecryptStream(inputStream, "Data\private_key.pem", mem)
   End Using
   ' mem can be read
 End Sub
End Class

Decrypt bytes

Decrypting byte arrays obtained from CMS/PKCS7 Enveloped Data will produce the raw decrypted 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 DecryptCmsBytes
{
 public void Demo(byte[] envelopedData)
 {
  OpenSslCms cms = new OpenSslCms();
 
  string privateKey = @"Data\private_key.pem";
  byte[] decryptedData = cms.DecryptBytes(envelopedData, 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(pemEnvelopedData As Byte())
  Dim cms As New OpenSslCms()
 
  Dim privateKey As String = "Data\private_key.pem"
  Dim decryptedData As Byte() = cms.DecryptBytes(envelopedData, 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 directly 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 DecryptCmsWithX509Certificate2
{
 public static void Demo()
 {
  OpenSslCms cms = new OpenSslCms();
  X509Certificate2 cert2 = new X509Certificate2(@"C:\Projects\didisoft.pkcs12.p12", "cobra34", X509KeyStorageFlags.Exportable);
 
  cms.DecryptFile(@"Data\enveloped.dat", cert2, @"Data\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 DecryptCmsWithX509Certificate2
 Public Shared Sub Demo()
  Dim cert2 As New X509Certificate2("C:\Projects\didisoft.pkcs12.p12", "cobra34", X509KeyStorageFlags.Exportable)
 
  Dim cms As New OpenSslCms()
  cms.DecryptFile("Data\enveloped.dat", cert2, "Data\Output.txt")
 End Sub
End Class

Exception handling

All the methods provided from the OpenSslCms class throw DidiSoft.OpenSsl.OpenSslException. Specific sub classes of this exception can be caught beforehand in order to identify more thoroughly what went wrong or 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
OpenSslCms cms = new OpenSslCms();
try 
{
 cms.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 an Enveloped 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
OpenSslCms cms = new OpenSslCms();
try 
{
 cms.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 for decryption is not an Enveloped Data
}
catch (DidiSoft.OpenSsl.OpenSslException e)
{
// general OpenSSL error
}

Summary

This chapter illustrated how to use DidiSoft.OpenSsl.Cms.OpenSslCms class to encrypt and decrypt CMS/PKCS#7 Enveloped Data. All the methods demonstrated here will produce content compatible with the popular OpenSSL software and its cms command with specified -ouform paramter. The decryption method provided from the OpenSslCms class will automatically determine if the encrypted data is in PEM or DER format.