Cryptography in C# — Asymmetric and Symmetric Encryption

Cryptography in C# — Asymmetric and Symmetric Encryption

Introduction

Cryptography has always been seen as a complex and intricate concept. While this is true in terms of the actual algorithms entailed, in most modern languages, C# inclusive we have a convenient and relatively stress-free method of implementing cryptographic concerns.

The code for this blog can be obtained here.

What is cryptography?

Cryptography is simply the process of encrypting a message with a key, to be decrypted by a final consumer.

There are three major classes of cryptographic algorithms; symmetric encryption, asymmetric encryption, and hash functions.

In this article, we will be expounding on symmetric encryption and asymmetric encryption. Hash functions will be covered in part 2 of this series.

Uses of cryptography

  • confidentiality: making the data only readable by the intended consumers.

  • Data integrity: ensuring the data wasn't mutated in an unidentified manner.

  • Non-repudiation: assurance that a party to a contract cannot deny the authenticity of a message that originates with them.

  • Authentication: ensuring only the right person acts.

Symmetric Encryption

Symmetric encryption is a type of encryption in which both the sender and the receiver use the same key to encrypt and decrypt the message.

Example: Jane wants to share an encrypted message with Dave, Jane encrypts this message using a key. She then shares this same key with Dave, who uses it to decrypt and read the message.

There are various types of symmetric encryption. In this article, we will cover DES/Triple DES and AES algorithms.

Examples of Symmetric Algorithms

DES and Triple DES Symmetric Algorithms

DES — Data Encryption Standard is a symmetric encryption algorithm that encrypts using 56 bits of data. The DES algorithm was broken, and as a result, came Triple DES; which encrypted and decrypted data with three keys.

Both DES and Triple DES are not recommended in modern applications, given the advancement of computational power made in the last few decades. We wouldn't be coding this because of this reason.

AES Symmetric Algorithms

AES (Advanced encryption standard) is a successor to the DES mentioned above after it was broken. Rather than the 56-bit key in DES, AES uses a 128, 192, or 256-bit key; making it less susceptible to brute force attacks.

AES is based on the Rigndaal cipher which is a series of encryption algorithms.

There are different modes of AES encryption with differing advantages and disadvantages. Some of the modes are:

  • ECB mode: Electronic Code Book mode

  • CBC mode: Cipher Block Chaining mode

  • CFB mode: Cipher Feedback mode

  • OFB mode: Output Feedback mode

  • CTR mode: Counter mode

  • GCM mode: Galois Counter Mode

We won't go into details about the different modes as that is beyond the scope of this article.

As part of this article, we will implement the AES algorithm with the CBC mode. (Note: The implementations of the other modes have a similar interface as the CBC modes)

Implementing AES with the CBC mode:

Step 1: Create a class and import necessary namespaces:

using System.Security.Cryptography;

Step 2: Create your encrypt method

Note the EncrypteCbc method.

public static byte[] Encrypt(byte[] data, byte[] key, byte[] initializationVector)
{
    using var aes = Aes.Create();
    aes.Key = key;
    var encryptedData = aes.EncryptCbc(data, initializationVector);

    return encryptedData;
}

Step 3: Create your decrypt method

Note the DecryptCbc method.

public static byte[] Decrypt(byte[] data,  byte[] key, byte[] initializationVector)
{
    using var aes = Aes.Create();
    aes.Key = key;
    var decryptedData = aes.DecryptCbc(data, initializationVector);

    return decryptedData;
}
  • "data": serialized data to be encrypted

  • "key": the key used for encryption

  • "initilizationVector": used to create variability in the encrypted data such that the same data encrypted with the same key would not produce the same output.

Step 4: Test your implementation

In the main method of your code, call your encrypt class as follows:


void TestSymmetricEncryption()
{
    var message = "Hello world!";

// generate our initialization vector
    var initializationVector = RandomNumberGenerator.GetBytes(16);

// generate our key
    var key = RandomNumberGenerator.GetBytes(32);

// encrypt our data
    var encryptedData = AesEncryption.Encrypt(message.ToBytes(), key, initializationVector);
    Console.WriteLine(Convert.ToBase64String(encryptedData));

// decrypt our data
    var decryptedData = AesEncryption.Decrypt(encryptedData, key, initializationVector);
    Console.WriteLine(decryptedData.BytesToString());
}

Please note that the ToBytes() and BytesToString() methods are extension methods:

public static byte[] ToBytes(this string input)
{
    return Encoding.UTF8.GetBytes(input);
}

public static string BytesToString(this byte[] data)
{
    return Encoding.UTF8.GetString(data);
}

Advantages of Symmetric encryption:

  • it is extremely secure

  • it is relatively fast, compared to asymmetric encryption.

Disadvantages of Symmetric encryption:

  • Key sharing - for the receiver to decrypt the message, they have to have the same key used by the sender in the encryption. This is problematic because we need to be concerned about how to securely share the key and the negative impact that might ensue if the key is compromised.

Asymmetric Encryption

Asymmetric encryption attempts to solve the problem of key sharing in symmetric encryption by using two mathematically linked keys to encrypt and decrypt the data respectively. The public key encrypts the data, and the private key decrypts the data.

The private key can't be determined from the public key. Thus to be able to decrypt the data one must have the private key.

Example of Asymmetric encryption

RSA Algorithm

The RSA algorithm (Rivest-Shamir-Adleman) is an asymmetric algorithm; it facilitates the encryption and decryption of data with both private and public keys.

Implementing RSA Algorithm

Step 1: Create a new class and import the necessary namespaces

using System.Security.Cryptography;

Step 2: Create our encryptor and initialize it with the key size in bits (in our case 2048 bits - 256 bytes)

private readonly RSA _rsa; 

public RsaEncryption()
{
    _rsa = RSA.Create(2048);
}

Step 3: Create a method that is responsible for encrypting our data

public byte[] Encrypt(string dataToEncrypt)
{
    return _rsa.Encrypt(dataToEncrypt.ToBytes(), RSAEncryptionPadding.OaepSHA256);
}

Step 4: Create a method that is responsible for decrypting our data

public byte[] Decrypt(byte[] dataToDecrypt)
{
    return _rsa.Decrypt(dataToDecrypt, RSAEncryptionPadding.OaepSHA256);
}

Step 5: Create methods that facilitate the export/import of our public keys

public byte[] ExportPublicKey()
{
    return _rsa.ExportRSAPublicKey();
}

public void ImportPublicKey(byte[] publicKey)
{
    _rsa.ImportRSAPublicKey(publicKey, out _);
}

Step 6: Create a method that exports our private key

public byte[] ExportPrivateKey(int numberOfIterations, string password)
    {
        var keyParams = new PbeParameters(
            PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, numberOfIterations);

        var encryptedPrivateKey = _rsa.ExportEncryptedPkcs8PrivateKey(
            password.ToBytes(), keyParams);

        return encryptedPrivateKey;
    }

Step 7: Testing our implementation


void TestAsymmetricEncryption()
{
    // initialize our encryptor
    var rsa = new RsaEncryption();

    // create our private key
    byte[] encryptedPrivateKey = rsa.ExportPrivateKey(100000, "bjbh2j3hbjbjk33iui5br");

    // export our public key
    byte[] publicKey = rsa.ExportPublicKey();
    // our data to encrypt
    const string original = "Hello World!";

    // encrypt our data
    var encrypted = rsa.Encrypt(original);

    // create new encryptor to decrypt our data
    var rsa2 = new RsaEncryption();

    // import our public key
    rsa2.ImportPublicKey(publicKey);

    // import the mathematically linked private key
    rsa2.ImportEncryptedPrivateKey(encryptedPrivateKey, "bjbh2j3hbjbjk33iui5br");

    // decrypt our data
    var decrypted = rsa2.Decrypt(encrypted).BytesToString();
}

Advantages of Asymmetric encryption:

  • It mitigates the effect of key sharing by providing two keys for this purpose.

Disadvantages of Asymmetric encryption:

  • The size of data to encrypt is limited.

Hybrid Encryption

Due to the disadvantage of asymmetric encryption (limitation of data size), a common approach is to encrypt our data with symmetric encryption and in turn, encrypt our symmetric key with asymmetric encryption.

Implementation of Hybrid Encryption

Step 1: Create a new class "HybridEncryption.cs" for hybrid encryption and import the useful namespaces.

Step 2: Create a new class "EncryptionPacket.cs" to package our encryption parameters and include the following properties.

public class EncryptionPacket
{
    public byte[] EncryptedSessionKey;
    public byte[] EncryptedData;
    public byte[] InitializationVector;
}

Step 3: Create a method "EncryptData" which will be responsible for encrypting our data.

public static EncryptionPacket EncryptData(byte[] original, AsymmetricEncryption.RsaEncryption rsaParams)
{
    // Generate our session key.
    var encryptionKey = RandomNumberGenerator.GetBytes(32);

    // Create the encrypted packet and generate the IV.
    var encryptedPacket = new EncryptionPacket { InitializationVector = RandomNumberGenerator.GetBytes(16) };

    // Encrypt our data with AES.
    encryptedPacket.EncryptedData = AesEncryption.Encrypt(original, encryptionKey, encryptedPacket.InitializationVector);

    // Encrypt the session key with RSA
    encryptedPacket.EncryptedSessionKey = rsaParams.Encrypt(encryptionKey);

    return encryptedPacket;
}

Step 4: Create a method "DecryptData" for the decryption of our data.

public static byte[] DecryptData(EncryptionPacket encryptedPacket, AsymmetricEncryption.RsaEncryption rsaEncryptor)
{
    // Decrypt AES Key with RSA.
    var decryptedSessionKey = rsaEncryptor.Decrypt(encryptedPacket.EncryptedSessionKey);

    // Decrypt our data with  AES using the decrypted session key.
    var decryptedData = AesEncryption.Decrypt(encryptedPacket.EncryptedData,
        decryptedSessionKey, encryptedPacket.InitializationVector);

    return decryptedData;
}

Step 5: Testing our implementation

void TestHybridEncryption()
{
    Console.WriteLine("********* Initializing Hybrid encryption test");
    const string message = "Hello World!";
    Console.WriteLine($"Original data: {message}");

    // initialize our rsa encryptor
    var rsaEncryptor = new RsaEncryption();

    // encrypt our data
    var encryptionPacket = HybridEncryption.EncryptData(message.ToBytes(), rsaEncryptor);
    Console.WriteLine($"Encrypted data: {Convert.ToBase64String(encryptionPacket.EncryptedData)}");

    // decrypt our data
    var decrypted = HybridEncryption.DecryptData(encryptionPacket, rsaEncryptor).BytesToString();
    Console.WriteLine($"Decrypted data: {decrypted}");
}

Advantages of Hybrid encryption:

  • It solves the problem of both symmetric and asymmetric encryption by addressing the problem of the size limitation of data to encrypt and eliminating the problem of sharing sensitive keys.

Disadvantages of Hybrid encryption:

  • It is slower than asymmetric and symmetric encryption because it combines both of them.