Chapter 20 - Cryptography
Windows Data Protection
byte[] original = { 1, 2, 3, 4, 5 };
original.Dump ("Original");
DataProtectionScope scope = DataProtectionScope.CurrentUser;
byte[] encrypted = ProtectedData.Protect (original, null, scope);
encrypted.Dump("Encrypted");
byte[] decrypted = ProtectedData.Unprotect (encrypted, null, scope);
// decrypted is now {1, 2, 3, 4, 5}
decrypted.Dump("Decrypted");
Hashing files and strings
byte[] hash;
// Compute hash from file:
File.WriteAllText ("test.txt", @"
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.");
using (Stream fs = File.OpenRead ("test.txt"))
hash = SHA1.Create().ComputeHash (fs); // SHA1 hash is 20 bytes long
hash.Dump ("Hash from test.txt");
// Compute hash from string:
byte[] data = System.Text.Encoding.UTF8.GetBytes ("stRhong%pword");
byte[] hash2 = SHA256.Create().ComputeHash (data);
hash2.Dump ("Hash from string");
Hashing passwords with Pbkdf2
byte[] encrypted = KeyDerivation.Pbkdf2 (
password: "stRhong%pword",
salt: Encoding.UTF8.GetBytes ("j78Y#p)/saREN!y3@"),
prf: KeyDerivationPrf.HMACSHA512,
iterationCount: 100,
numBytesRequested: 64);
encrypted.Dump();
AES Encrypt and Decrypt
byte[] key = { 145, 12, 32, 245, 98, 132, 98, 214, 6, 77, 131, 44, 221, 3, 9, 50 };
byte[] iv = { 15, 122, 132, 5, 93, 198, 44, 31, 9, 39, 241, 49, 250, 188, 80, 7 };
byte[] data = { 1, 2, 3, 4, 5 }; // This is what we're encrypting.
using (SymmetricAlgorithm algorithm = Aes.Create())
using (ICryptoTransform encryptor = algorithm.CreateEncryptor (key, iv))
using (Stream f = File.Create ("encrypted.bin"))
using (Stream c = new CryptoStream (f, encryptor, CryptoStreamMode.Write))
c.Write (data, 0, data.Length);
byte[] decrypted = new byte [5];
using (SymmetricAlgorithm algorithm = Aes.Create())
using (ICryptoTransform decryptor = algorithm.CreateDecryptor (key, iv))
using (Stream f = File.OpenRead ("encrypted.bin"))
using (Stream c = new CryptoStream (f, decryptor, CryptoStreamMode.Read))
for (int b; (b = c.ReadByte()) > -1;)
Console.Write (b + " "); // 1 2 3 4 5
AES In-Memory Cryptography
void Main()
{
byte[] key = new byte [16];
byte[] iv = new byte [16];
var cryptoRng = RandomNumberGenerator.Create();
cryptoRng.GetBytes (key);
cryptoRng.GetBytes (iv);
Console.WriteLine ($"Key: {string.Join ("", key.Select (b => b.ToString ("x2")))}");
Console.WriteLine ($"IV : {string.Join ("", iv.Select (b => b.ToString ("x2")))}");
string toEncrypt = "There are 10 kinds of people. Those that understand binary, and those that don't.";
byte[] encrypted = MemCrypt.Encrypt (Encoding.UTF8.GetBytes (toEncrypt), key, iv);
Console.WriteLine ($"Encrypted: {string.Join ("", encrypted.Select (b => b.ToString ("x2")))}");
byte[] decrypted = MemCrypt.Decrypt (encrypted, key, iv);
Console.WriteLine ($"Decrypted: {Encoding.UTF8.GetString (decrypted)}");
string encrypted2 = MemCrypt.Encrypt ("Yeah!", key, iv);
Console.WriteLine (encrypted2); // R1/5gYvcxyR2vzPjnT7yaQ==
string decrypted2 = MemCrypt.Decrypt (encrypted2, key, iv);
Console.WriteLine (decrypted2); // Yeah!
Array.Clear (key, 0, key.Length);
Array.Clear (iv, 0, iv.Length);
Console.WriteLine ($"Key: {string.Join ("", key.Select (b => b.ToString ("x2")))}");
Console.WriteLine ($"IV : {string.Join ("", iv.Select (b => b.ToString ("x2")))}");
}
class MemCrypt
{
#if NET6
// From .NET 6, you can use the EncryptCbc method to shortcut the process:
public static byte[] Encrypt (byte[] data, byte[] key, byte[] iv)
{
using Aes algorithm = Aes.Create();
algorithm.Key = key;
return algorithm.EncryptCbc (data, iv);
}
// From .NET 6, we can use the DecryptCbc method to shortcut the process:
public static byte[] Decrypt (byte[] data, byte[] key, byte[] iv)
{
using Aes algorithm = Aes.Create();
algorithm.Key = key;
return algorithm.DecryptCbc (data, iv);
}
#else
public static byte[] Encrypt (byte[] data, byte[] key, byte[] iv)
{
using (Aes algorithm = Aes.Create())
using (ICryptoTransform encryptor = algorithm.CreateEncryptor (key, iv))
return Crypt (data, encryptor);
}
public static byte[] Decrypt (byte[] data, byte[] key, byte[] iv)
{
using (Aes algorithm = Aes.Create())
using (ICryptoTransform decryptor = algorithm.CreateDecryptor (key, iv))
return Crypt (data, decryptor);
}
static byte[] Crypt (byte[] data, ICryptoTransform cryptor)
{
MemoryStream m = new MemoryStream();
using (Stream c = new CryptoStream (m, cryptor, CryptoStreamMode.Write))
c.Write (data, 0, data.Length);
return m.ToArray();
}
#endif
// Additional overloads that accept strings:
public static string Encrypt (string data, byte[] key, byte[] iv)
{
return Convert.ToBase64String (
Encrypt (Encoding.UTF8.GetBytes (data), key, iv));
}
public static string Decrypt (string data, byte[] key, byte[] iv)
{
return Encoding.UTF8.GetString (
Decrypt (Convert.FromBase64String (data), key, iv));
}
}
Chaining Streams
byte[] key = new byte [16];
byte[] iv = new byte [16];
var cryptoRng = RandomNumberGenerator.Create();
cryptoRng.GetBytes (key);
cryptoRng.GetBytes (iv);
using (Aes algorithm = Aes.Create())
{
using (ICryptoTransform encryptor = algorithm.CreateEncryptor(key, iv))
using (Stream f = File.Create ("serious.bin"))
using (Stream c = new CryptoStream (f, encryptor, CryptoStreamMode.Write))
using (Stream d = new DeflateStream (c, CompressionMode.Compress))
using (StreamWriter w = new StreamWriter (d))
await w.WriteLineAsync ("Small and secure!");
using (ICryptoTransform decryptor = algorithm.CreateDecryptor(key, iv))
using (Stream f = File.OpenRead ("serious.bin"))
using (Stream c = new CryptoStream (f, decryptor, CryptoStreamMode.Read))
using (Stream d = new DeflateStream (c, CompressionMode.Decompress))
using (StreamReader r = new StreamReader (d))
Console.WriteLine (await r.ReadLineAsync()); // Small and secure!
}
RSA - Default KeySize
byte[] data = { 1, 2, 3, 4, 5 }; // This is what we're encrypting.
using (var rsa = new RSACryptoServiceProvider())
{
rsa.KeySize.Dump();
byte[] encrypted = rsa.Encrypt (data, true);
byte[] decrypted = rsa.Decrypt (encrypted, true);
}
RSA - Save Keys to XML
using (var rsa = new RSACryptoServiceProvider())
{
File.WriteAllText ("PublicKeyOnly.xml", rsa.ToXmlString (false));
rsa.ToXmlString (false).Dump("PublicKeyOnly.xml");
File.WriteAllText ("PublicPrivate.xml", rsa.ToXmlString (true));
rsa.ToXmlString (true).Dump("PublicPrivate.xml");
}
RSA - Encrypting and Decrypting
// Create public/private keypair, and save to disk:
using (var rsa = new RSACryptoServiceProvider())
{
File.WriteAllText ("PublicKeyOnly.xml", rsa.ToXmlString (false));
File.WriteAllText ("PublicPrivate.xml", rsa.ToXmlString (true));
}
// Encrypt. Note that we can directly encrypt only small messages.
byte[] data = Encoding.UTF8.GetBytes ("Message to encrypt");
string publicKeyOnly = File.ReadAllText ("PublicKeyOnly.xml");
string publicPrivate = File.ReadAllText ("PublicPrivate.xml");
byte[] encrypted, decrypted;
using (var rsaPublicOnly = new RSACryptoServiceProvider())
{
rsaPublicOnly.FromXmlString (publicKeyOnly);
encrypted = rsaPublicOnly.Encrypt (data, true);
// The next line would throw an exception because you need the private
// key in order to decrypt:
// decrypted = rsaPublicOnly.Decrypt (encrypted, true);
}
// Decrypt:
using (var rsaPublicPrivate = new RSACryptoServiceProvider())
{
// With the private key we can successfully decrypt:
rsaPublicPrivate.FromXmlString (publicPrivate);
decrypted = rsaPublicPrivate.Decrypt (encrypted, true);
Encoding.UTF8.GetString (decrypted).Dump ("decrypted");
}
RSA - Signing
byte[] data = Encoding.UTF8.GetBytes ("Message to sign");
byte[] publicKey;
byte[] signature;
object hasher = SHA1.Create(); // Our chosen hashing algorithm.
// Generate a new key pair, then sign the data with it:
using (var publicPrivate = new RSACryptoServiceProvider())
{
signature = publicPrivate.SignData (data, hasher);
publicKey = publicPrivate.ExportCspBlob (false); // get public key
}
// Create a fresh RSA using just the public key, then test the signature.
using (var publicOnly = new RSACryptoServiceProvider())
{
publicOnly.ImportCspBlob (publicKey);
Console.WriteLine (publicOnly.VerifyData (data, hasher, signature)); // True
// Let's now tamper with the data, and recheck the signature:
data [0] = 0;
Console.WriteLine (publicOnly.VerifyData (data, hasher, signature)); // False
// The following throws an exception as we're lacking a private key:
signature = publicOnly.SignData (data, hasher);
}