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.
Subscribe to my newsletter
Read articles from Joshua Akosa directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by