A fast-track to secure encryption: Using the AWS Encryption SDK

20/05/2019
Posted in AWS Blog
20/05/2019 Ben Bridts

On GitHub you can find more than 130 repositories under the AWS organisation, and multiple other organisations that are managed by AWS teams, it can be easy to miss some great projects. Some of those are very AWS-specific like cfn-lint, a must-have for CloudFormation users. Others are very clearly made to be independent like Firecracker, which lives in its own separate organisation.

In this blogpost we’re going to talk about the AWS Encryption SDK (and CLI), a tool that is useful both in- and outside of an AWS account. We will look at some basic use cases, but also explain how you can use it to make sure you can decrypt in multiple regions or take advantage of additional authenticated data (AAD).

What is the AWS Encryption SDK?

The AWS documentation describes it as “a client-side encryption library designed to make it easy for everyone to encrypt and decrypt data using industry standards and best practices.”, and while best-practices for encryption are already a great selling point, it comes with a lot more features:

  • Available as an SDK for Java, Python, or C.
  • Available as a Command Line tool.
  • Comes with a default, secure configuration.
  • Works out of the box with KMS (the AWS Key Management Service).
  • Can be extended to use your own Master Key Provider, so there is no dependency on any AWS service.
  • Has documented, standardised output, so you can use different sdk’s to decrypt and encrypt.

Basic Usage

The encryption SDK uses enveloppe encryption to encrypt the data. This means that we have a unique data key (DK) to encrypt each message, and encrypt that data key with a master key. This allows us to store the encrypted data key with the encrypted message, and use a service like AWS KMS to protect the master key and do the decryption of the DK in a Hardware Security Module (HSM). The documentation goes in a bit more detail. This also means that we need to specify the master key to use. In our examples we will be using KMS to do this, and we create a Customer Master Key (CMK) with the `aws kms create-key` command, this will give you an `arn` to use.

Let’s look at how we would encrypt a simple string in python (after installing the sdk with `pip install aws-encryption-sdk`):

    import aws_encryption_sdk
    
    cmk_id = 'arn:aws:kms:eu-west-1:123456789012:key/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
    kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[cmk_id])
    
    message = b'Some secret data'
    
    ciphertext, header = aws_encryption_sdk.encrypt(
        source=message,
        key_provider=kms_key_provider,
    )

The returned ciphertext is all you need to store to make it possible to get your original secret back. You still need the KMS master key provider, to give the sdk a way to call KMS, but you do not need to provide the key id.

    import aws_encryption_sdk
    
    kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider()
    
    ciphertext = b'...'
    
    plaintext, header = aws_encryption_sdk.decrypt(
        source=ciphertext,
        key_provider=kms_key_provider,
    )
    
    print(plaintext)

Multi-region support

You may have noticed that we passed a list of key ids to the KMS key provider in the example above. In the most basic form we only have one element in the list, but we can add multiple key ids, to make sure we can still decrypt even if KMS is down in one region.

to enable this, we only need to change the configuration of the Key Provider, the encrypt and decrypt calls stay exactly the same:

    import aws_encryption_sdk
    
    cmk_id_ireland = 'arn:aws:kms:eu-west-1:123456789012:key/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
    cmk_id_frankfurt = 'arn:aws:kms:eu-central-1:123456789012:key/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'
    kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[
        cmk_id_ireland, cmk_id_frankfurt
    ])
    
    // everything below stays the same

This works by generating one data key, encrypting the message with that key, and encrypting the data key twice. Once with each key. Because both DKs are stored with the ciphertext, the decrypt function can retry the decryption with the other CMK if the first one would fail.

Another advantage of this enveloppe encryption, is that the message (which could be a lot of data) is only encrypted under one key. This means that the extra space needed is only the space an encrypted key takes, and not related to the size of the message.

Additional Authenticated Data

If you looked at the logging of KMS calls in CloudTrail, you may have noticed that some of them contain an “Encryption Context”. This is Additional Authenticated Data (AAD). This data is public data (it’s logged in CloudTrail, and stored in plaintext next to the encrypted secret), but typically used so that it’s impossible to decrypt the ciphertext without specifying the same data as when the encryption was done.

This does not give you an additional security just by including it (as the AAD is public), but can be used to provide logging or enforce policies at the Master Key level.

Take for example this way of encrypting

    import aws_encryption_sdk
    
    cmk_id = 'arn:aws:kms:eu-west-1:123456789012:key/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
    kms_key_provider = aws_encryption_sdk.KMSMasterKeyProvider(key_ids=[cmk_id])
    
    message = b'Some secret data'
    
    ciphertext, header = aws_encryption_sdk.encrypt(
        source=message,
        key_provider=kms_key_provider,
        encryption_context={
            'creator': 'ben',
            'group': 'everyone',
        }
    )

This would allow us to search in CloudTrail for every encryption/decryption where the creator was `ben` , allowing us to see which IAM principals are reading my secrets.

We could also change the KMS Key Policy to only allow the decryption under certain conditions. For example, this statement gives the rights to decrypt, but only for decrypt actions that have the “everyone” group in their encryption context.

    {
        "Sid": "Only decrypt the everyone group",
        "Effect": "Allow",
        "Principal": {
            "AWS": "arn:aws:iam::123456789012:root"
        },
        "Action": [
            "kms:Decrypt",
            "kms:ReEncryptFrom"
        ],
        "Resource": "*",
        "Condition": {
            "StringEquals": {
                "kms:EncryptionContext:group": "ben"
            }
        }
    }

Again, because the SDK uses a standardised format, the decrypt call stays the same, The Encryption context is stored with the ciphertext

Learn more

This post was an introduction to the encryption sdk, there are a lot more things it can do. We will try to highlight some of them in a future blogpost, but in the meantime you can read the documentation or look at examples in the readme of the sdks.

  • SHARE

Leave a Reply

Your email address will not be published. Required fields are marked *

LET'S WORK
TOGETHER

Need a hand? Or a high five?
Feel free to visit our offices and come say hi
… or just drop us a message

We are ready when you are

Cloudar NV – BE

Veldkant 7
2550 Kontich (Antwerp)
Belgium

info @ cloudar.be

+32 3 450 67 18

VAT BE0564 763 890

Cloudar BV – NL

Van Deventerlaan 31-51
3528 AG Utrecht
The Netherlands

info @ cloudar.nl

+32 3 450 67 18

VAT NL864471099B01

    This contact form is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

    contact
    • SHARE