How to use symmetric and asymmetric encryption in C#

By FoxLearn 1/2/2025 7:27:51 AM   122
This article teaches how to secure data in .NET applications by encrypting and decrypting it using either a single key (symmetric encryption) or a public/private key pair (asymmetric encryption).

Symmetric encryption uses a single key for both encryption and decryption, while asymmetric encryption involves a pair of keys one for encryption and another for decryption.

Encryption is used to secure data either "at rest" (stored data) or "in motion" (data being transmitted).

Symmetric encryption vs. asymmetric encryption

Encryption comes in two types: symmetric and asymmetric. Both methods help protect sensitive data, whether it's stored or transmitted.

In C#, the System.Security.Cryptography namespace provides built-in support for both types. Symmetric encryption uses one key for both encryption and decryption, making it faster but less secure since anyone with the key can decrypt the data. Asymmetric encryption uses a public key for encryption and a private key for decryption, offering stronger security, though it is slower.

Symmetric encryption is best for large data (e.g., email) due to its efficiency, while asymmetric encryption is suited for small amounts of sensitive data like passwords or credit card numbers.

Implementing symmetric encryption in C#

To implement symmetric encryption, you need to generate a 256-bit key for encrypting and decrypting data. While symmetric encryption is faster than asymmetric encryption, it is less secure because the same key is used for both encryption and decryption.

In the example, you are instructed to create a static C# class called SymmetricEncryptionDecryptionManager in a file named SymmetricEncryptionDecryptionManager.cs for managing the encryption and decryption process.

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

namespace EncryptionDemo
{
    public class SymmetricEncryptionDecryptionManager
    {
        private const int KeySize = 32; // 256 bits for AES-256
        private const int IvSize = 16;  // AES block size (128 bits)

        // Helper method for creating the AES encryptor/decryptor
        private static ICryptoTransform CreateAesTransform(string key, byte[] iv, bool isEncrypting)
        {
            using (Aes aes = Aes.Create())
            {
                aes.Key = GetValidKey(key);
                aes.IV = iv;
                return isEncrypting ? aes.CreateEncryptor() : aes.CreateDecryptor();
            }
        }

        // Ensures the key is exactly 256 bits (32 bytes)
        private static byte[] GetValidKey(string key)
        {
            byte[] keyBytes = Encoding.UTF8.GetBytes(key);
            Array.Resize(ref keyBytes, KeySize); // Ensure key size is 256 bits (32 bytes)
            return keyBytes;
        }

        // Generates a random initialization vector
        private static byte[] GenerateRandomIv()
        {
            byte[] iv = new byte[IvSize];
            using (var rng = new RNGCryptoServiceProvider())
            {
                rng.GetBytes(iv);
            }
            return iv;
        }

        // Encrypts the data using symmetric AES encryption
        public static string Encrypt(string data, string key)
        {
            byte[] iv = GenerateRandomIv(); // Generate random IV
            using (var memoryStream = new MemoryStream())
            {
                using (var encryptor = CreateAesTransform(key, iv, true))
                using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                using (var streamWriter = new StreamWriter(cryptoStream))
                {
                    streamWriter.Write(data);
                }

                byte[] encryptedData = memoryStream.ToArray();
                byte[] result = new byte[IvSize + encryptedData.Length];
                Buffer.BlockCopy(iv, 0, result, 0, IvSize); // Prepend IV to the encrypted data
                Buffer.BlockCopy(encryptedData, 0, result, IvSize, encryptedData.Length);
                return Convert.ToBase64String(result);
            }
        }

        // Decrypts the ciphertext using symmetric AES decryption
        public static string Decrypt(string cipherText, string key)
        {
            byte[] cipherBytes = Convert.FromBase64String(cipherText);
            byte[] iv = new byte[IvSize];
            byte[] encryptedData = new byte[cipherBytes.Length - IvSize];

            // Extract IV and encrypted data
            Buffer.BlockCopy(cipherBytes, 0, iv, 0, IvSize);
            Buffer.BlockCopy(cipherBytes, IvSize, encryptedData, 0, encryptedData.Length);

            using (var memoryStream = new MemoryStream(encryptedData))
            {
                using (var decryptor = CreateAesTransform(key, iv, false))
                using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                using (var streamReader = new StreamReader(cryptoStream))
                {
                    return streamReader.ReadToEnd();
                }
            }
        }
    }
}

In this example, we defines two methods: Encrypt and Decrypt. The Encrypt method takes the data and a secret key, using the AES algorithm for symmetric encryption. The encrypted data is returned as a Base64 string. The Decrypt method accepts the encrypted data and the secret key, converting the cipher text into a byte array to decrypt it. The decrypted data is then returned as a string. These methods can be used to encrypt and decrypt data by calling them from the SymmetricEncryptionDecryptionManager class.

var encryptedText = SymmetricEncryptionDecryptionManager.Encrypt("This is sample text.", key);
Console.WriteLine(encryptedText);
var decryptedText = SymmetricEncryptionDecryptionManager.Decrypt(encryptedText, key);
Console.WriteLine(decryptedText);

Implementing asymmetric encryption in C#

In asymmetric encryption, two keys are used: a public key for encryption and a private key for decryption. Both keys are required to implement this encryption method. To encrypt data, you first generate a public/private key pair, typically using the RSA algorithm.

var rsa = new RSACryptoServiceProvider();
string publicKeyXML = rsa.ToXmlString(false);
string privateKeyXML = rsa.ToXmlString(true);

After generating the key pair, you can use the public key to encrypt data.

byte[] data = Encoding.UTF8.GetBytes("Hello world!");
byte[]encryptedData = rsa.Encrypt(data, false);

To decrypt the data, you will need to use the private key.

byte[] decryptedData = rsa.Decrypt(encryptedData, false);
string message = Encoding.UTF8.GetString(decryptedData);

Create a new class called AsymmetricEncryptionDecryptionManager with the provided code to handle asymmetric encryption and decryption.

using System;
using System.Security.Cryptography;
using System.Text;

public class AsymmetricEncryptionDecryptionManager
{
    // Encrypts the given data using the provided RSA parameters
    public static string Encrypt(string data, RSAParameters rsaParameters)
    {
        using (var rsa = new RSACryptoServiceProvider())
        {
            rsa.ImportParameters(rsaParameters);
            byte[] byteData = Encoding.UTF8.GetBytes(data);
            byte[] encryptedData = rsa.Encrypt(byteData, RSAEncryptionPadding.OaepSHA256); // Secure padding
            return Convert.ToBase64String(encryptedData);
        }
    }

    // Decrypts the encrypted data using the provided RSA parameters
    public static string Decrypt(string cipherText, RSAParameters rsaParameters)
    {
        using (var rsa = new RSACryptoServiceProvider())
        {
            byte[] cipherDataAsByte = Convert.FromBase64String(cipherText);
            rsa.ImportParameters(rsaParameters);
            byte[] decryptedData = rsa.Decrypt(cipherDataAsByte, RSAEncryptionPadding.OaepSHA256); // Secure padding
            return Encoding.UTF8.GetString(decryptedData);
        }
    }
}

You can use the following code snippet to encrypt and decrypt data by calling the Encrypt and Decrypt methods of the AsymmetricEncryptionDecryptionManager class.

var rsaCryptoServiceProvider = new RSACryptoServiceProvider(2048); // Initialize RSA with 2048-bit key
var cipherText = AsymmetricEncryptionDecryptionManager.Encrypt("This is sample text.", rsaCryptoServiceProvider.ExportParameters(false)); // Encrypt with public key
Console.WriteLine(cipherText); // Output the encrypted text

var plainText = AsymmetricEncryptionDecryptionManager.Decrypt(cipherText, rsaCryptoServiceProvider.ExportParameters(true)); // Decrypt with private key
Console.WriteLine(plainText); // Output the decrypted (original) text

You can combine both the SymmetricEncryptionDecryptionManager and AsymmetricEncryptionDecryptionManager into a single class by writing overloaded methods for symmetric and asymmetric encryption and decryption.