Invitation Digital Tech Blog

Building Scalable & Responsive Architecture

By

Encryption in .NET with Amazon's Key Management Service

We care about keeping details of our customer’s purchases secure, that’s why we employ various cryptographic techniques to keep sensitive information from prying eyes. For this we follow industry best practices when encrypting and decrypting data.

Encryption Is Easy

The System.Security.Cryptography namespace in .NET contains lots of classes to help you keep your data secret. If you’re interested in symmetric encryption that uses the same key to both encrypt and decrypt data, then the Advanced Encryption Standard (AES) is where you should start, in .NET the Aes Class is the base class for implementations of this algorithm.

Assuming you have a two streams, an input stream and an output stream, and a byte array containing the key you would like to use, the two C# methods below will encrypt and decrypt arbitrary lengths of data, you’ll need to add a using for System.IO and System.Security.Cryptography

void Encrypt(Stream input, Stream output, byte[] key)
{
    using (var algorithm = Aes.Create())
    {
        algorithm.Key = key;
        output.Write(algorithm.IV, 0, algorithm.IV.Length);
        using (var cryptoStream = new CryptoStream(output,
            algorithm.CreateEncryptor(), CryptoStreamMode.Write))
        {
            input.CopyTo(cryptoStream);
            cryptoStream.FlushFinalBlock();
        }
    }
}

void Decrypt(Stream input, Stream output, byte[] key)
{
    using (var algorithm = Aes.Create())
    {
        algorithm.Key = key;
        var iv = algorithm.IV;
        input.Read(iv, 0, iv.Length);
        algorithm.IV = iv;
        using (var cryptoStream = new CryptoStream(input,
            algorithm.CreateDecryptor(), CryptoStreamMode.Read))
        {
            cryptoStream.CopyTo(output);
        }
    }
}

By default, the Aes class uses a mode of operation called Cipher Block Chaining (CBC), this takes the output of the previous block and uses bitwise XOR on the current block of plaintext before encrypting or decrypting the data. This ensures that even if you have repetitive data, the encrypted output does not show this repetition. The Wikipedia article on Block cipher mode of operation has a striking example of how plaintext patterns can be left in the ciphertext when you encrypt just the blocks of plaintext, otherwise known as Electronic Codebook (ECB) mode.

In order to use CBC you always need some ciphertext to XOR with the plaintext before you encrypt, and you won’t have this when encrypting the very first block. That’s where an Initialisation Vector (IV) comes in. The CryptoStream will use this IV and XOR it with the very first block before encrypting it (the Aes class will create a new random IV in its constructor, you can call the GenerateIV method if you want a new IV after this). The IV will need to be remembered if we ever want to decrypt the first block again and this is why we write it to the beginning of the output stream, here’s the relevant line in the Encrypt method:

output.Write(algorithm.IV, 0, algorithm.IV.Length);

When decrypting we read this from the input stream and set the algorithm’s IV, this is done in these lines in the Decrypt method:

var iv = algorithm.IV;
input.Read(iv, 0, iv.Length);
algorithm.IV = iv;

Encryption Is Hard

This will use 256-bit encryption with an algorithm developed and tested by a community of experts and is in use worldwide. The British intelligence organisation (GCHQ) that first cracked the German Enigma code in World War II admit they can’t crack this type of encryption, even with their vast computing power and expert cryptanalysts. However, that doesn’t make AES fool-proof. The security in this algorithm rests entirely in the key you use, if someone can find out your key, they can access all the data you’ve encrypted with that key, not only that, they can encrypt their own data and pass it off as yours.

Where are you going to store your key so you can use it to encrypt and decrypt your data? If you put it in a config file then this will be visible to everyone who has access to your source code, if you use a distributed version control system like git, then your key could be on computers you have no control over with little protection from attackers. Sure, they’d need access to your source code, but once someone knows your key they have it forever. What happens when a colleague leaves your organisation? You have no way of auditing who has the key, who they have given it to nor anyway way to revoke access. You could choose to encrypt the key itself, but what key are you going to use to encrypt the key? Now all your adversary needs to do is find out this key which just moves the problem up a level, it doesn’t fix the issue or protect your data.

Encryption Using KMS

This is where the AWS Key Management Service (KMS) can help. You can create an encryption key in the Identity and Access Management (IAM) section of your AWS account and allow only certain users or roles to be key users or administrators. Amazon guarantee the key itself won’t ever leave the region in which it is created, they provide the ability to audit key usage and even integrate with other Amazon services like EBS, S3 and RDS.

We can use KMS to create data keys, these are then used to encrypt our data. When we ask KMS for a data key we also get a copy of the data key encrypted using our master key. We can store this encrypted key along with our encrypted message, the message can’t be decrypted unless you can decrypt the data key and the data key can’t be decrypted unless you have access to the master key. In the example below I have re-written the previous Encrypt and Decrypt methods to take advantage of KMS. This time instead of passing in a byte array containing the key you pass in the ARN of the key. You will need to add a reference to the AWS SDK, which is available as a NuGet Package, then you’ll need to add a using for Amazon.KeyManagementService and Amazon.KeyManagementService.Model.

void Encrypt(Stream input, Stream output, string arn)
{
    using (var kms = new AmazonKeyManagementServiceClient())
    using (var algorithm = Aes.Create())
    {
        var dataKey = kms.GenerateDataKey(new GenerateDataKeyRequest
        {
            KeyId = arn,
            KeySpec = DataKeySpec.AES_256
        });
        output.WriteByte((byte)dataKey.CiphertextBlob.Length);
        dataKey.CiphertextBlob.CopyTo(output);
        algorithm.Key = dataKey.Plaintext.ToArray();
        output.Write(algorithm.IV, 0, algorithm.IV.Length);
        using (var cryptoStream = new CryptoStream(
            output,
            algorithm.CreateEncryptor(),
            CryptoStreamMode.Write))
        {
            input.CopyTo(cryptoStream);
            cryptoStream.FlushFinalBlock();
        }
    }
}

void Decrypt(Stream input, Stream output, string arn)
{
    using (var kms = new AmazonKeyManagementServiceClient())
    using (var algorithm = Aes.Create())
    {
        var length = input.ReadByte();
        var buffer = new byte[length];
        input.Read(buffer, 0, length);
        var decryptedData = kms.Decrypt(new DecryptRequest
        {
            CiphertextBlob = new MemoryStream(buffer)
        });
        algorithm.Key = decryptedData.Plaintext.ToArray();
        var iv = algorithm.IV;
        input.Read(iv, 0, iv.Length);
        algorithm.IV = iv;
        using (var cryptoStream = new CryptoStream(input,
            algorithm.CreateDecryptor(), CryptoStreamMode.Read))
        {
            cryptoStream.CopyTo(output);
        }
    }
}

There’s no error checking in these methods, I’ll leave it as an exercise to the reader to improve that. Nevertheless, they will work as is and provide industry best practice encryption techniques to your codebase, along with the ability to audit key usage, limit access to certain members of staff and revoke access, all via the Amazon Web Services API.