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);
}
C# 12 in a Nutshell
Buy from amazon.com Buy print or Kindle edition
Buy from ebooks.com Buy PDF edition
Buy from O'Reilly Read via O'Reilly subscription