---
title: "Encrypt and decrypt with CMS in C#"
url: https://recursiveai.org/onecrypto/cookbook/encrypt-file-cms/
description: "Encrypt and decrypt a file with CMS in C# (the S/MIME and .p7m format) for a recipient’s certificate, using AES-256-GCM and RSA-OAEP, and exchange encrypted files with openssl cms both ways. Runnable code with OneCrypto 0.1.0-preview.1."
version: "0.1.0-preview.1"
---

# Encrypt and decrypt with CMS in C#

`AuthEnvelopedData.Create` encrypts data for the holder of a certificate, in the CMS format that S/MIME mail and `.p7m` files use; `Decrypt` opens it with the private key. The content is encrypted with AES-256-GCM, which detects any change, and the AES key is wrapped with RSA-OAEP for the recipient. The program below encrypts for a certificate made by OpenSSL and also opens a file OpenSSL encrypted.

```shell
dotnet new console -o CmsEncrypt && cd CmsEncrypt
dotnet add package RecursiveAI.OneCrypto --version 0.1.0-preview.1
openssl req -x509 -newkey rsa:3072 -nodes -days 365 -subj "/CN=Alice" \
  -keyout alice-key.pem -out alice.pem
echo "Q3 numbers: draft 2" > report.txt
openssl cms -encrypt -binary -aes-256-gcm -recip alice.pem \
  -keyopt rsa_padding_mode:oaep -in report.txt -outform DER -out from-openssl.p7m
```

OneCrypto is a preview, so `dotnet add package` needs `--version` or `--prerelease`. Put this in `Program.cs` and run `dotnet run`:

```csharp
using System.Text;
using RecursiveAI.OneCrypto;

using LoadedItems certFile = Crypto.Load("alice.pem");
Certificate alice = certFile.Certificates[0];

// Encrypt for Alice: AES-256-GCM content, AES key wrapped with RSA-OAEP (SHA-256)
byte[] plaintext = File.ReadAllBytes("report.txt");
AuthEnvelopedData message = AuthEnvelopedData.Create(plaintext,
    [CmsRecipient.KeyTransport(alice)]);
File.WriteAllBytes("report.p7m", message.Encoded.ToArray());
Console.WriteLine($"Encrypted report.p7m: {message.Encoded.Length} bytes, " +
    $"{message.RecipientInfos.Count} recipient");

// Decrypt with Alice's private key and certificate
using LoadedItems keyFile = Crypto.Load("alice-key.pem");
using CmsDecryptionKey key = CmsDecryptionKey.FromKey(
    keyFile.PrivateKeys[0].Decryption!, alice);
foreach (string path in new[] { "report.p7m", "from-openssl.p7m" })
{
    byte[] opened = AuthEnvelopedData.Parse(File.ReadAllBytes(path)).Decrypt(key);
    Console.WriteLine($"{path,-17} {Encoding.UTF8.GetString(opened).Trim()}");
}
```

```text
Encrypted report.p7m: 616 bytes, 1 recipient
report.p7m        Q3 numbers: draft 2
from-openssl.p7m  Q3 numbers: draft 2
```

Add more `CmsRecipient` entries to encrypt one message for several people; each opens it with their own key. EC certificates use `CmsRecipient.KeyAgreement`, and `CmsRecipient.Password` encrypts with a password instead of a certificate. `EncryptStream` and `DecryptStream` take streams.

## Check it with OpenSSL

OpenSSL decrypts the file OneCrypto wrote:

```shell
openssl cms -decrypt -binary -inform DER -in report.p7m \
  -recip alice.pem -inkey alice-key.pem
```

```text
Q3 numbers: draft 2
```

## When decryption fails

Decryption fails closed: no plaintext is returned until the whole message is decrypted and the GCM tag has verified. A wrong key, a damaged key or a changed byte all raise the same `CmsParseException` with `CmsError.DecryptionFailed`; only “no recipient in this message is meant for this key” is told apart.

## Limits

- **Not audited.** This library has not been independently audited. It is a preview: 0.1.0-preview.1, and the API can change.
- **Memory.** AES-GCM is one-shot on .NET, so `AuthEnvelopedData` holds the whole message in memory, up to 1 GiB by default (`CmsLimits.MaxContentBytes`). For larger files, split them into chunks, or use `EnvelopedData`, which streams with AES-CBC but cannot detect every change: the project recommends AuthEnvelopedData where authenticity matters.
- **No sender identity.** Encryption says nothing about who sent the file. Sign it too if that matters: [Sign a file with CMS](https://recursiveai.org/onecrypto/cookbook/sign-file-cms/).
- **Readers.** AuthEnvelopedData (RFC 5083) is newer than EnvelopedData; OpenSSL 3 reads it, but check older S/MIME clients before relying on it.

Every recipient type, limit and the OpenSSL test matrix: [Cryptographic Message Syntax](https://recursiveai.org/onecrypto/docs/cms/).
