Symmetric algorithms in .net work utilizing "cipher block chaining"; this works by taking data that's bigger then a predefined block size and braking it up into blocks of equal size; if the last block is too small it's padded with data.
The first block is encrypted using the Initialization Vector and the key, the following block is then encrypted using the result from the first block instead of the IV but using the same key, and so on until all the blocks are encrypted.
The reason this is done is to ensure that different data that has the same blocks does not encrypt to the same result, and thus can't be reversed engineered.
using System;
using System.Security.Cryptography;
using System.Text;
namespace pc.symmetricEncryptionExample
{
class Program
{
static byte[]
Encrypt(string data, out byte[] Key, out byte[] Iv)
{
var cryptoAlgorythm = SymmetricAlgorithm.Create("Rijndael");
//create and
set IV & Key
cryptoAlgorythm.GenerateIV();
Iv = cryptoAlgorythm.IV;
cryptoAlgorythm.GenerateKey();
Key = cryptoAlgorythm.Key;
ICryptoTransform encryptor =
cryptoAlgorythm.CreateEncryptor(Key, Iv);
var plainData = Encoding.ASCII.GetBytes(data);
return encryptor.TransformFinalBlock(plainData, 0, plainData.Length);
}
static byte[]
Decrypt(byte[] Data, byte[] Key, byte[] Iv) {
var cryptoAlgorythm = SymmetricAlgorithm.Create("Rijndael");
ICryptoTransform decryptor =
cryptoAlgorythm.CreateDecryptor(Key, Iv);
return decryptor.TransformFinalBlock(Data, 0, Data.Length);
}
static void Main(string[] args)
{
var data = "Hello world, this is my
secret data i want encrypted";
byte[] iv;
byte[] key;
//encrypted
data
byte[] cipherData = Encrypt(data, out key, out iv);
//display
encrypted data
Console.WriteLine("Encrypted data");
Console.WriteLine(Encoding.ASCII.GetString(cipherData));
//display key
Console.WriteLine("Key");
Console.WriteLine(Encoding.ASCII.GetString(key));
//display
Decrypted Data
var decryptedData = Decrypt(cipherData, key, iv);
Console.WriteLine("Decrypted data");
Console.WriteLine(Encoding.ASCII.GetString(decryptedData));
}
}
}
So let's do that, first let's find out what the criteria for our password must be, luckily the SymmetricAlgorithm Class has a function for just that
var cryptoAlgorythm = SymmetricAlgorithm.Create("Rijndael");
foreach (var ks in cryptoAlgorythm.LegalKeySizes)
Console.WriteLine($"{ks.MinSize}
{ks.MaxSize} {ks.SkipSize}");
a bit convoluted? sure is, doesn't help that there's a bug that'll let you pass invalid key lengths (< 128) lengths, but there's a ValidKeyLength(size in bits) function.
if (cryptoAlgorythm.ValidKeySize(Key.Length * 8))
Console.WriteLine("Valid key size");
Now let's create a function to take in a password with size in bits parameter and return a byte array we can use as our key in our encryption function.
static byte[]
GetPassword(string
Password, int RequiredSizeInBits)
{
var password = Encoding.Default.GetBytes(Password);
if (RequiredSizeInBits > (password.Length * 8))
Array.Resize(ref
password, (RequiredSizeInBits / 8));
return password;
}
using System;
using System.Security.Cryptography;
using System.Text;
namespace pc.symmetricEncryptionExample
{
class Program
{
static byte[]
Encrypt(string data, byte[] Key, out byte[] Iv)
{
var cryptoAlgorythm = SymmetricAlgorithm.Create("Rijndael");
//create and
set IV & Key
cryptoAlgorythm.GenerateIV();
Iv = cryptoAlgorythm.IV;
if (cryptoAlgorythm.ValidKeySize(Key.Length * 8))
Console.WriteLine("Valid");
ICryptoTransform encryptor =
cryptoAlgorythm.CreateEncryptor(Key, Iv);
var plainData = Encoding.ASCII.GetBytes(data);
return
encryptor.TransformFinalBlock(plainData, 0, plainData.Length);
}
static byte[]
Decrypt(byte[] Data, byte[] Key, byte[] Iv)
{
var cryptoAlgorythm = SymmetricAlgorithm.Create("Rijndael");
ICryptoTransform decryptor = cryptoAlgorythm.CreateDecryptor(Key, Iv);
return decryptor.TransformFinalBlock(Data, 0, Data.Length);
}
static byte[]
GetPassword(string Password,
int RequiredSizeInBits)
{
var password = Encoding.Default.GetBytes(Password);
if (RequiredSizeInBits > (password.Length * 8))
Array.Resize(ref
password, (RequiredSizeInBits / 8));
return password;
}
static void Main(string[] args)
{
var data = "Hello world, this is my
secret data i want encrypted";
var password = "pass";
var Iv = new byte[16];
var Key = GetPassword(password, 128);
//encrypted
data
byte[] cipherData = Encrypt(data, Key, out Iv);
//display
encrypted data
Console.WriteLine("Encrypted data");
Console.WriteLine(Encoding.ASCII.GetString(cipherData));
//display
Decrypted Data
var decryptedData = Decrypt(cipherData, Key, Iv);
Console.WriteLine("Decrypted data");
Console.WriteLine(Encoding.ASCII.GetString(decryptedData));
}
}
}
Now this is a pretty contrived example, obviously to be more secure you wouldn't want just a four letter pass in a big byte array.
In our examples we've been using the static SymmetricAlgorithm.Create("Rijndael"); function, however there are many different options for Symmetric Encryption:
- RijndaelManaged
- DESCryptoServiceProvider
- RC2CryptoServiceProvider
- TripleDESCryptoServiceProvider
- AesManaged
now despite all of these options which all derive from the SymmetricAlgorithm class, AesManaged is the gold standard, it's the ISO standard and the ubiquitous preference around the world.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace pc.symmetricEncryptionExample03
{
class Program
{
static byte[]
Encrypt(string data, byte[] Key, out byte[] Iv)
{
using (var
aesAlgorythm = new AesManaged())
try {
aesAlgorythm.GenerateIV();
Iv = aesAlgorythm.IV;
aesAlgorythm.Key = Key;
var encryptor = aesAlgorythm.CreateEncryptor();
using (var ms = new MemoryStream()) {
using (var cs=new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
using (var sw = new StreamWriter(cs))
sw.Write(data);
return ms.ToArray(); }
}
finally {
aesAlgorythm.Clear();
}
}
static string Decrypt(byte[] EncryptedData, byte[] Key, byte[] Iv)
{
using (var
aesAlgorythm = new AesManaged())
try {
var decryptor =
aesAlgorythm.CreateDecryptor(Key, Iv);
using (var ms = new MemoryStream(EncryptedData))
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
using (var sr = new StreamReader(cs))
return sr.ReadToEnd();
}
finally {
aesAlgorythm.Clear();
}
}
static byte[]
GetPassword(string
Password, int RequiredSizeInBits)
{
var password = Encoding.Default.GetBytes(Password);
if (RequiredSizeInBits > (password.Length * 8))
Array.Resize(ref
password, (RequiredSizeInBits / 8));
return password;
}
static void Main(string[] args)
{
var data = "Hello world, this is my
secret data i want encrypted";
var password = "pass";
var Iv = new byte[16];
var Key = GetPassword(password, 128);
//encrypted
data
byte[] cipherData = Encrypt(data, Key, out Iv);
//display
encrypted data
Console.WriteLine("Encrypted data");
Console.WriteLine(Encoding.Default.GetString(cipherData));
//display
Decrypted Data
Console.WriteLine("Decrypted data");
Console.WriteLine(Decrypt(cipherData, Key, Iv));
}
}
}
above we leveraged the AesManaged class for our encryption, one thing to notics is that we call the clear method in a finally clause, this 0's out any potentially sensitive data like our key for example.