Symmetric encrypt/decrypt in .NET

Viewed 49499

I am after a symmetric encryption/decryption routine in C#. I know there have been a few questions on this topic before, but most of the answers seem to be about the philosophy of encryption rather than giving actual code.

Update: I'd really like to see some code, rather than just links. Many thanks!

8 Answers

Look at the example code at the bottom of this page.

Copy-pasting it here:

int Rfc2898KeygenIterations= 100;
int AesKeySizeInBits = 128;
String Password = "VerySecret!";
byte[] Salt = new byte[16];
System.Random rnd = new System.Random(); 
rnd.NextBytes(Salt);
byte[] rawPlaintext = System.Text.Encoding.Unicode.GetBytes("This is all clear now!");
byte[] cipherText= null;
byte[] plainText= null;
using (Aes aes = new AesManaged())
{
    aes.Padding = PaddingMode.PKCS7;
    aes.KeySize = AesKeySizeInBits;
    int KeyStrengthInBytes= aes.KeySize/8;
    System.Security.Cryptography.Rfc2898DeriveBytes rfc2898 =
        new System.Security.Cryptography.Rfc2898DeriveBytes(Password, Salt, Rfc2898KeygenIterations);
    aes.Key = rfc2898.GetBytes(KeyStrengthInBytes);
    aes.IV = rfc2898.GetBytes(KeyStrengthInBytes);
    using (MemoryStream ms = new MemoryStream())
    {
        using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
        {
            cs.Write(rawPlaintext, 0, rawPlaintext.Length);
        }
        cipherText= ms.ToArray();
    }

    using (MemoryStream ms = new MemoryStream())
    {
        using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write))
        {
            cs.Write(cipherText, 0, cipherText.Length);
        }
        plainText = ms.ToArray();
    }
}
string s = System.Text.Encoding.Unicode.GetString(plainText);
Console.WriteLine(s);

Well for starters keys are not strings, keys are binary blobs. PlainText is the same, it's not actually text, again it's a binary blob.

Now of course you can convert strings to byte arrays using Encoding.UTF8.GetBytes(message), however when converting keys back and forth it's a little more complicated, you usually use Convert.ToBase64String and Convert.FromBase64String.

Don't forget that block ciphers also need one more thing, the Initialization Vector, so really your method signatures should be

byte[] Encrypt(byte[] plainText, byte[] key, byte[] iv)

byte[] Decrypt(byte[] cipherText, byte[] key, byte[] iv)

The key and IVs must be cryptographically secure random numbers, don't just type them and don't use C#'s Random function. The size of the key and the IV depend on the cipher algorithm used, and can be accessed by the properties on the classes.

To generate a CSRPNG you do something like

RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] key = new byte[algorithm.KeySizeValue / 8];
rng.GetBytes(key);
byte[] iv = new byte[algorithm.BlockSizeValue / 8];
rng.GetBytes(iv);

You can also use the Rfc2898DeriveBytes class to derive a key and IV from a password and a salt, but again the salt should be a cryptographically secure random number. You should also note when you create a symmetric algorithm a secure key and IV is generated for you.

This way you can then choose the correct encoding for your text, be it UTF8, ASCII or whatever. The links have enough samples so cutting and pasting in here is rather pointless.

Posting from MSDN documetation for AES symmetric algorithm:

using System;
using System.IO;
using System.Security.Cryptography;

namespace Aes_Example
{
    class AesExample
    {
        public static void Main()
        {
            string original = "Here is some data to encrypt!";

            // Create a new instance of the AesCryptoServiceProvider
            // class.  This generates a new key and initialization
            // vector (IV).
            using (AesCryptoServiceProvider myAes = new AesCryptoServiceProvider())
            {
                // Encrypt the string to an array of bytes.
                byte[] encrypted = EncryptStringToBytes_Aes(original, myAes.Key, myAes.IV);

                // Decrypt the bytes to a string.
                string roundtrip = DecryptStringFromBytes_Aes(encrypted, myAes.Key, myAes.IV);

                //Display the original data and the decrypted data.
                Console.WriteLine("Original:   {0}", original);
                Console.WriteLine("Round Trip: {0}", roundtrip);
            }
        }
        static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");
            byte[] encrypted;

            // Create an AesCryptoServiceProvider object
            // with the specified key and IV.
            using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create an encryptor to perform the stream transform.
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for encryption.
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }

            // Return the encrypted bytes from the memory stream.
            return encrypted;
        }

        static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("IV");

            // Declare the string used to hold
            // the decrypted text.
            string plaintext = null;

            // Create an AesCryptoServiceProvider object
            // with the specified key and IV.
            using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create a decryptor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for decryption.
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {

                            // Read the decrypted bytes from the decrypting stream
                            // and place them in a string.
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }
            }

            return plaintext;
        }
    }
}

This approach uses strings for an encryption password and the encrypted text. It's useful for interoperating with non-.NET code that requires strings.

It uses the OEM United States encoding for converting between binary data and strings so that the only transformation is to add or remove an extra 0 byte in each unicode characters (versus UTF8 encoding, which interprets characters' bytes dynamically).

public class AesEncryptor {
    readonly Encoding encoding = Encoding.GetEncoding(437); // Using the OEM United States encoding since it provides symmetric transform for 0..255.
    readonly AesManaged aes;

    /// <param name="password">Should be at least 32 characters.</param>
    public AesEncryptor(string password) {
        const int keySize = 256;
        const int saltSize = 16;
        var derivedBytes = new Rfc2898DeriveBytes(password, saltSize);
        aes = new AesManaged { Padding = PaddingMode.PKCS7, KeySize = keySize, Key = derivedBytes.GetBytes(keySize / 8), IV = derivedBytes.Salt };
    }

    public string Encrypt(string text) => CryptoTransform(text, aes.CreateEncryptor());
    public string Decrypt(string text) => CryptoTransform(text, aes.CreateDecryptor());

    string CryptoTransform(string text, ICryptoTransform cryptoTransform) {
        byte[] input = encoding.GetBytes(text);
        var stream = new MemoryStream();
        using (var cryptoStream = new CryptoStream(stream, cryptoTransform, CryptoStreamMode.Write))
            cryptoStream.Write(input, 0, input.Length);
        return encoding.GetString(stream.ToArray());
    }
}
Related