Skip to content
This repository has been archived by the owner on Apr 1, 2024. It is now read-only.

Crypto Recipes

Andrew Arnott edited this page Feb 5, 2019 · 15 revisions

If it isn't documented here, there's a very good chance it is still available in the library. The documentation below is incomplete.

A couple of very common mistakes made by would-be cryptographers:

  1. Using a key that is not one of the allowed sizes. What are the allowed sizes?

  2. Picking an algorithm that does not include padding and then not supplying data that exactly fills a cipher block. Which algorithms include padding?

Create a buffer filled with cryptographically strong random data
// WinRT-like API
uint length = 15;
byte[] cryptoRandomBuffer = WinRTCrypto.CryptographicBuffer.GenerateRandom(length);

// .NET Framework-like API
byte[] cryptoRandomBuffer = new byte[15];
NetFxCrypto.RandomNumberGenerator.GetBytes(cryptoRandomBuffer);
Generate a random number
uint random = WinRTCrypto.CryptographicBuffer.GenerateRandomNumber();
uint randomFrom0To15 = WinRTCrypto.CryptographicBuffer.GenerateRandomNumber() % 16;
Derive a symmetric key from a password

To use crypto APIs with a user-supplied password, you must at least convert the password into a byte buffer. But it's more secure to run it through a key derivation algorithm, using this technique:

string password; // comes in from the user.
byte[] salt; // best initialized to a unique value for each user, and stored with the user record
int iterations = 5000; // higher makes brute force attacks more expensive
int keyLengthInBytes;
byte[] key = NetFxCrypto.DeriveBytes.GetBytes(password, salt, iterations, keyLengthInBytes);

// A similar technique is possible using WinRT-emulated API via the `WinRTCrypto.KeyDerivationAlgorithmProvider`.
Get the hash for a buffer (SHA1, SHA256, etc.)
byte[] data;
var hasher = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha1);
byte[] hash = hasher.HashData(data);
string hashBase64 = Convert.ToBase64String(hash);
Get the MAC for a buffer (HMAC-SHA1, HMAC-SHA256, etc.)
byte[] keyMaterial;
byte[] data;
var algorithm = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha1);
CryptographicHash hasher = algorithm.CreateHash(keyMaterial);
hasher.Append(data);
byte[] mac = hasher.GetValueAndReset();
string macBase64 = Convert.ToBase64String(mac);
Perform AES encryption/decryption
byte[] keyMaterial;
byte[] data;
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var key = provider.CreateSymmetricKey(keyMaterial);

// The IV may be null, but supplying a random IV increases security.
// The IV is not a secret like the key is.
// You can transmit the IV (w/o encryption) alongside the ciphertext.
var iv = WinRTCrypto.CryptographicBuffer.GenerateRandom(provider.BlockLength);

byte[] cipherText = WinRTCrypto.CryptographicEngine.Encrypt(key, data, iv);

// When decrypting, use the same IV that was passed to encrypt.
byte[] plainText = WinRTCrypto.CryptographicEngine.Decrypt(key, cipherText, iv);
Encrypt/decrypt a file

This method will perform encrypt or decrypt a file, depending on its arguments:

static async Task CryptoTransformFileAsync(string sourcePath, string destinationPath, ICryptoTransform[] transforms, CancellationToken cancellationToken = default)
{
    const int BufferSize = 4096;
    using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, useAsync: true))
    {
        using (var destinationStream = new FileStream(destinationPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, BufferSize, useAsync: true))
        {
            using (var cryptoStream = CryptoStream.WriteTo(destinationStream, transforms))
            {
                await sourceStream.CopyToAsync(cryptoStream, BufferSize, cancellationToken);
                await cryptoStream.FlushAsync(cancellationToken);
                cryptoStream.FlushFinalBlock();
            }
        }
    }
}

The following method calls the above method to encrypt then decrypt a file.

private const string beforeFilePath = @"d:\temp\before.txt";
private const string cipherFilePath = @"d:\temp\cipher.txt";
private const string afterFilePath = @"d:\temp\after.txt";

static async Task EncryptThenDecryptAsync()
{
    ISymmetricKeyAlgorithmProvider aesGcm = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
    byte[] keyMaterial = WinRTCrypto.CryptographicBuffer.GenerateRandom(32);
    var cryptoKey = aesGcm.CreateSymmetricKey(keyMaterial);
    var encryptor = new ICryptoTransform[] { WinRTCrypto.CryptographicEngine.CreateEncryptor(cryptoKey) };
    var decryptor = new ICryptoTransform[] { WinRTCrypto.CryptographicEngine.CreateDecryptor(cryptoKey) };
    await CryptoTransformFileAsync(beforeFilePath, cipherFilePath, encryptor);
    await CryptoTransformFileAsync(cipherFilePath, afterFilePath, decryptor);
}