.NET Crypto API

The .NET framework ships with a suite of cryptographic classes that perform symmetric encryption, asymmetric encryption, and hashing. These are organized in a class hierarchy to support replacing one alrogithm with another. This is incredibly important, as algorithms and key lengths change to keep up with the pace of hardware.

Symmetric Algorithms

Symmetric algorithms are designed for keeping messages confidential. They can be applied to messages of any length. You use the same key to encrypt the message as to decrypt it, so the shared key must be kept secret.

The current standard for symmetric encryption is AES, the Advanced Encryption Standard. This algorithm works in 128, 192, and 256 bit key lengths. Choose the key length that performs well on your platform, but is sufficient to protect the data according to its value.

Create an AesCryptoServiceProvider and set the KeySize property to your selected key length. The class will generate a key of the selected length using a cryptographically secure pseudo random number generator. If you are trying to generate a key, then you can simply use this one.

The provider will also produce a random initialization vector. This ensures that the beginning of the message is sufficiently randomized, so that attackers cannot compare messages to look for patterns.

SymmetricAlgorithm aes = new AesCryptoServiceProvider();
aes.KeySize = 256;

aes.Key.Length.Should().Be(32);     // 256 / 8
aes.IV.Length.Should().Be(16);      // 128 / 8

To encrypt a message, create a CryptoStream to decorate an output stream.

MemoryStream memoryStream = new MemoryStream();

var cryptoStream = new CryptoStream(
    memoryStream,
    aes.CreateEncryptor(),
    CryptoStreamMode.Write);
using (var writer = new StreamWriter(cryptoStream))
{
    writer.Write(message);
}

return memoryStream.ToArray();

To decrypt, provide the key and initialization vector to the CryptoStream.

SymmetricAlgorithm provider = new AesCryptoServiceProvider();
MemoryStream memoryStream = new MemoryStream(encryptedMessage);

var cryptoStream = new CryptoStream(
    memoryStream,
    provider.CreateDecryptor(key, iv),
    CryptoStreamMode.Read);
using (var reader = new StreamReader(cryptoStream))
{
    return reader.ReadToEnd();
}

The key and initialization vector that were randomly created by the AES crypto service provider will be replaced by your own.

Asymmetric Algorithms

The key that you used to encrypt a message must be exchanged with the recipient in a secure manner. The best way to do so is to encrypt the key using the recipient's public key. That way, only the recipient, with their private key, can decrypt the message.

So we must first create an RSA key pair. We do so by creating an instance of the RSACryptoServiceProvider class and supplying the desired key length. 2048 is the minimum viable key length right now, but 4096 is safer.

var rsa = new RSACryptoServiceProvider(2048);
byte[] blob = rsa.ExportCspBlob(includePrivateParameters: false);

To give the public key to another party, export the CSP blob without the private parameters. This captures only the public key as a binary array. Then the other party can import the CSP blob into an RSACryptoServiceProvider in order to use your public key.

var publicKey = new RSACryptoServiceProvider();
publicKey.ImportCspBlob(blob);

At this point, the other party can encrypt the symmetric key that they just used to encrypt a message,

byte[] encryptedKey = publicKey.Encrypt(aes.Key, true);

The "true" parameter tells the Encrypt function to pad the AES key with some random bytes to protect against a chosen plaintext attack. Always pass "true" as the second parameter.

When the recipient receives the encrypted key, he decrypts it with his RSA key pair (which includes the private key). Remember that this was generated randomly when the RSACryptoServiceProvider was first created. If you need to store it long term, use ExportCspBlob(true). Take extra precautions with that blob, as it contains your private key!

byte[] decryptedKey = rsa.Decrypt(encryptedKey, true);

Again, we pass "true" to use the secure padding algorithm. This will find the padding and strip it off, leaving you with the AES key that was used to encrypt the message. Combine this key with the initialization vector, which can be exchanged in the clear with no loss of confidentiality.

Digital Signatures

Finally, an asymmetric algorithm can be combined with a hash function to digitally sign a message. As before, generate an RSA key and exchange the public key with another party. Then, you can sign a message with your private key.

string message = "Alice knows Bob's secret.";
var memory = new MemoryStream();
using (var writer = new StreamWriter(memory))
{
    writer.Write(message);
}

var hashFunction = new SHA256CryptoServiceProvider();
byte[] signature = rsa.SignData(memory.ToArray(), hashFunction);

And now the other party can verify that you sent the message, using only your public key.

bool verified = publicKey.VerifyData(
    memory.ToArray(), hashFunction, signature);
verified.Should().BeTrue();

The .NET Crypto API offers a set of simple pluggable strategies for encrypting, decrypting, and signing messages. They are reviewed and audited for vulnerabilities so you can feel safe using them. And they are very easy to use, once you know which algorithms and options to select.