AWS Security Blog

How to decrypt ciphertexts in multiple regions with the AWS Encryption SDK in C

November 1, 2021: AWS KMS is replacing the term customer master key (CMK) with AWS KMS key and KMS key. The concept has not changed. To prevent breaking changes, AWS KMS is keeping some variations of this term. More info.


You’ve told us that you want to encrypt data once with AWS Key Management Service (AWS KMS) and decrypt that data with AWS KMS keys (KMS keys) that you specify, often with KMS keys in different AWS Regions. Doing this saves you compute resources and helps you to enable secure and efficient high-availability schemes.

The AWS Crypto Tools team has introduced the AWS Encryption SDK for C so you can achieve these goals. The new tool also adds more options for language and platform support and is fully interoperable with the implementations in Java and Python.

The AWS Encryption SDK is a client-side encryption library that helps make it easier for you to implement encryption best practices in your applications. You can use it with root keys from multiple sources, including KMS keys. The AWS Encryption SDK doesn’t require AWS KMS or any other AWS service.

You can use AWS KMS APIs directly to encrypt data keys using multiple KMS keys, but the AWS Encryption SDK provides tools to make working with multiple KMS keys even easier, with everything you need stored in the Encryption SDK’s portable encrypted message format. The AWS Encryption SDK for C uses the concept of keyrings, which makes it easy to work with ciphertexts encrypted under multiple KMS keys.

In this post, I will walk you through an example using the new AWS Encryption SDK for C. I’ll focus on some highlights from example code in the context of what an example application deployment might look like. You can find the complete example code in this GitHub repository. As always, we welcome your comments and your contributions.

Example scenario

To add some context around the example code, assume that you have a data processing application deployed both in US West (Oregon) us-west-2 and EU Central (Frankfurt) eu-central-1. For added durability, this example application creates and encrypts data in us-west-2 before it’s copied to the eu-central-1 Region. You have assurance that you could decrypt that data in us-west-2 if needed, but you want to mitigate the case where the decryption service in us-west-2 is unavailable. So how do you ensure you can decrypt your data in the eu-central-1 region when you need to?

In this example, your data processing application uses the AWS Encryption SDK and AWS KMS to generate a 256-bit data key to encrypt content locally in us-west-2. The AWS Encryption SDK for C deletes the plaintext data key after use, but an encrypted copy of that data key is included in the encrypted message that the AWS Encryption SDK returns. This prevents you from losing the encrypted copy of the data key, which would make your encrypted content unrecoverable. The data key is encrypted under the KMS keys in each of the two regions in which you might want to decrypt the data in the future.

A best practice is to plan to decrypt data using in-region data keys and KMS keys. This reduces latency and simplifies the permissions and auditing properties of the decryption operation. The latency impact from the cross-region API calls occur only during the encryption operation.

In this scenario, the KMS key key policy permissions look like this:

  • To encrypt data, the AWS identity used by the data processing application in us-west-2 needs kms:GenerateDataKey permission on the us-west-2 KMS key and kms:Encrypt permission on the eu-central-1 KMS key. You can specify these permissions in a key policy or IAM policy. This will let the application create a data key in us-west-2 and encrypt the data key under KMS keys in both AWS Regions.
  • To decrypt data, the AWS identity used by the data processing application in us-west-2 needs kms:Decrypt permissions on the KMS key in us-west-2 or the KMS key in eu-central-1.

Encryption path

First, define variables for the Amazon Resource Names (ARNs) of your KMS keys in us-west-2 and eu-central-1. In the Encryption SDK for C, to encrypt, you can identify a KMS key by its KMS key ARN or the Alias ARN that is mapped to the KMS key ARN.


const char *KEY_ARN_US_WEST_2 = "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab";

const char *KEY_ARN_EU_CENTRAL_1 = "arn:aws:kms:eu-central-1:111122223333:key/0987dcba-09fe-87dc-65ba-ab0987654321";      
     

Now, use the KMS key ARNs to create a keyring. In the Encryption SDK, a keyring is used to generate, encrypt, and decrypt data keys under multiple KMS keys. You’ll create a KMS keyring configured to use multiple KMS key.


struct aws_cryptosdk_keyring *kms_keyring=Aws::Cryptosdk::KmsKeyring::Builder().Build(KEY_ARN_US_WEST_2, { KEY_ARN_EU_CENTRAL_1 });

When the AWS Encryption SDK uses this keyring to encrypt data, it calls GenerateDataKey on the first KMS key that you specify, and Encrypt on each of the remaining KMS keys that you specify. The result is a plaintext data key generated in us-west-2, an encryption of the data key using the KMS key in us-west-2, and an encryption of the data key using the KMS key in eu-central-1.

The plaintext data key that AWS KMS generated in us-west-2 is protected under a TLS session using only cipher suites that support forward-secrecy. The process of sending that same plaintext data key to the AWS KMS endpoint in eu-central-1 for encryption is also protected under a similar TLS session.

The Encryption SDK uses the data key to encrypt your data, and it stores the encrypted data keys with your encrypted content. The result is an encrypted message that can be decrypted using the KMS key in us-west-2 or the KMS key in eu-central-1.

Now that you understand what’s going to happen after you create the keyring, I’ll return to the code sample. Next, you need to create an encrypt-mode session with your keyring and pass in the CMM. In the AWS Encryption SDK for C, you use a session to encrypt a single plaintext message or decrypt a single ciphertext message, regardless of its size. The session maintains the state of the message throughout its processing.


struct aws_cryptosdk_session *session = aws_cryptosdk_session_new_from_keyring(alloc, AWS_CRYPTOSDK_ENCRYPT, kms_keyring);

With the keyring and encrypt-mode session, the data processing application can ask the Encryption SDK to encrypt the data under the KMS keys that you specified in two different AWS regions:


aws_cryptosdk_session_process(
    session,
    out_ciphertext,
    out_ciphertext_buf_sz,
    out_ciphertext_len,
    in_plaintext,
    in_plaintext_len,
    &in_plaintext_consumed))

The result is an encrypted message that contains the ciphertext and two encrypted copies of the same data key. One encrypted data key was encrypted by your KMS key in us-west-2 and other encrypted data key was encrypted by your KMS key in eu-central-1.

Decryption path

In the AWS Encryption SDK for C, you use keyrings for both encrypting and decrypting. You can use the same keyring for both, or you can use different keyrings for each operation.

Why would you want to use a different keyring for decryption? At a high level, encrypt keyrings specify all KMS keys that can decrypt the ciphertext. Decrypt keyrings constrain the KMS keys the application is permitted to use.

Reusing a keyring for both encrypt and decrypt mode can simplify your AWS Encryption SDK client configuration, but splitting the keyring and using different AWS KMS clients provides more flexibility to meet your security and architecture goals. The option you choose depends in part on the constraints you want to place on the KMS keys your application uses.

The Decrypt API in the AWS KMS service doesn’t permit you to specify a KMS key as a request parameter. But the AWS Encryption SDK lets you specify one or many KMS keys in a decryption keyring, or even discover which KMS keys to try automatically. I’ll discuss each option in the next section.

Decryption path 1: Use a specific KMS key

This keyring option configures the AWS Encryption SDK to use only a specified KMS key in the specified AWS Region. This implies that your data processing application will need kms:Decrypt permissions on that specific KMS key and your application will always call the same AWS KMS endpoints in the specified AWS Region. CloudTrail events from the Decrypt API will also only appear in the specified AWS Region.

You might use a specific KMS key when the user or application that is decrypting the data has kms:Decrypt permission on only one of the KMS keys that encrypted the data keys.

The KMS key that you specify to decrypt the data must be one of the KMS keys that was used to encrypt the data. Make sure that at least one of the KMS keys from your encrypt keyring is included in the decrypt keyring and that the caller has kms:Decrypt permission to use it.

In my example, I encrypted the data keys using KMS keys in us-west-2 and eu-central-1, so I’ll start decrypting in eu-central-1 because I want to have a specific decrypt instantiation of the data processing application dedicated to eu-central-1. Assume the eu-central-1 data processing application has configured AWS IAM credentials for a principal with permission to call the Decrypt operation on the eu-central-1 KMS key.

Configure a keyring that asks the AWS Encryption SDK to use the KMS key in eu-central-1 to decrypt:

Aws::Cryptosdk::KmsKeyring::Builder().Build(KEY_ARN_EU_CENTRAL_1)

The Encryption SDK reads the encrypted message, finds the encrypted data key that was encrypted using the KMS key in eu-central-1, and uses this keyring to decrypt.

Decryption path 2: Use any of several KMS keys

This keyring option configures the AWS Encryption SDK to try several specific KMS keys during its decryption attempts, stopping as soon as it succeeds. You should configure the AWS IAM credentials used by your data processing application to have kms:Decrypt permissions on each of the specified regional KMS keys.

Your application could end up calling multiple regional AWS KMS endpoints. CloudTrail events from the Decrypt API will appear in the AWS Region in which the decrypt operation succeeds, and in any of the other AWS Regions that the keyring attempts to use. The KMS key that you specify to decrypt the data must be one of the KMS keys that was used to encrypt the data. Make sure that at least one of the KMS keys from your encrypt keyring is included in the decrypt keyring and that the application has kms:Decrypt permission to use it.

You might define an encryption keyring that includes multiple KMS keys so that users with different permissions can decrypt the same message. For example, you might include in your encryption keyring keys in multiple AWS regions.

Here’s an example keyring constructed with multiple KMS keys:

Aws::Cryptosdk::KmsKeyring::Builder().Build(KEY_ARN_EU_CENTRAL_1, { KEY_ARN_US_WEST_2 })

The AWS Encryption SDK reads each of the encrypted data keys stored in the encrypted message in the order that they appear. For each data key, the Encryption SDK searches the keyring for the matching KMS key that encrypted it. If it finds that KMS key, the AWS Encryption SDK calls AWS KMS in the AWS Region where the KMS key exists to decrypt that data key, then uses that decrypted key to decrypt the message. If the decryption operation fails for any reason, the AWS Encryption SDK moves on to the next encrypted data key in the message and tries again.

The AWS Encryption SDK will try to decrypt the encrypted message in this way until either decryption succeeds, or the AWS Encryption SDK has attempted and failed to decrypt any of the encrypted data keys using the KMS keys specified in the keyring.

If this keyring configuration looks familiar, it’s because it’s similar to the configuration you used on the encrypt path when you encrypted under multiple KMS keys. The difference is this:

  • Encryption: The AWS Encryption SDK uses every KMS key in the keyring to encrypt the data key, and adds all of the encrypted data keys to the encrypted message.
  • Decryption: The AWS Encryption SDK attempts to decrypt one of the encrypted data key using only the KMS keys in the keyring. It stops as soon as it succeeds.

Decryption path 3: Strategic ARNs reduction using the Discovery keyring

The previous decryption paths required you to keep track of the exact KMS keys used during the encryption operation, which may suit your needs for security and event logging. But what if you want more flexibility? What if you want to change the KMS keys that you use in encryption operations without updating the data processing application that decrypts your data? You can configure a keyring that doesn’t specify KMS keys to use for decryption, but instead tries each KMS key that encrypted a data key until decryption succeeds or all referenced KMS keys fail. We call this configuration a KMS Discovery keyring.

A Discovery keyring is equivalent to a keyring that includes all of the same KMS keys that were used to encrypt the data, but it’s simpler and less error-prone. You might use a KMS Discovery keyring if you have no preference among the KMS keys that encrypted a data key, and don’t mind the latency tradeoffs of trying KMS keys in remote AWS Regions, or trying KMS keys that will fail a permissions check while searching for one that succeeds. You can think of the KMS Discovery keyring as a universal keyring that you can use and reuse in your applications in many AWS Regions.

When you use a KMS Discovery keyring, the AWS Encryption SDK reads each encrypted data key and discovers the ARN of the KMS key used to encrypt it. The AWS Encryption SDK then uses the configured IAM credentials to call AWS KMS in that KMS key’s AWS Region to decrypt the data key. The AWS Encryption SDK repeats that process until it has decrypted the data key or runs out of encrypted data keys to try. .


Aws::Cryptosdk::KmsKeyring::Builder().BuildDiscovery();

While KMS Discovery keyrings are simpler, you run the risk of having your data processing application make a cross-region call to an AWS KMS endpoint that adds unwanted latency. In my example, you might not want the decrypting application running in us-west-2 to wait for the AWS Encryption SDK to call AWS KMS in eu-central-1. To use only the KMS keys in a particular AWS Region to decrypt the data keys, create a KMS Regional Discovery keyring that specifies the AWS Region, but not the KMS key ARNs. In my example, the following keyring allows the AWS Encryption SDK to use only KMS keys in us-west-2.


Aws::Cryptosdk::KmsKeyring::Builder()
        .WithKmsClient(create_kms_client(Aws::Region::US_WEST_2)).BuildDiscovery());

Because this example KMS Regional Discovery keyring specifies a client for the us-west-2 AWS Region, not a KMS key ARN, the AWS Encryption SDK will only try to decrypt any encrypted data key it finds that was encrypted using any KMS key in us-west-2. If, for some reason, none of the encrypted data keys was encrypted using a KMS key in us-west-2, or the application decrypting the data doesn’t have permission to use KMS keys in us-west-2, the AWS Encryption SDK call to decrypt the message with this keyring fails and fails fast. This may provide you with more options for deterministic error handling.

Keep in mind that the KMS Regional Discovery keyring allows the AWS Encryption SDK to try the KMS key for each encrypted data key in the specified AWS Region. However, AWS KMS never uses a KMS key until it verifies that the caller has permission to perform the requested operation. If the application doesn’t have kms:Decrypt permission for any of the KMS keys that were used to encrypt the data keys, decryption fails.

Summary

Encrypting KMS data keys using multiple KMS keys provides a variety of options to decrypt ciphertexts to meet your security, auditing, and latency requirements. My examples show how encrypted messages can be decrypted by using KMS keys in multiple AWS Regions. You can also use the Encryption SDK with root keys supplied by a custom key management infrastructure independent of AWS.

The AWS Encryption SDK’s portable and interoperable encrypted message format makes it easier to combine multiple encrypted data keys with your encrypted data to support the decryption access scheme you want. The AWS Encryption SDK for C brings these utilities to a new, broader set of platform and application environments to complement the existing Java and Python versions.

You can find the AWS Encryption SDK for C on GitHub.

If you have feedback about this blog post, submit comments in the Comments section below. If you have questions about this blog post, start a new thread on the AWS Crypto Tools forum or contact AWS Support.

Want more AWS Security how-to content, news, and feature announcements? Follow us on Twitter.

Author

Liz Roth

Liz is a Senior Software Development Engineer at Amazon Web Services. She has been at Amazon for more than 8 years and has more than 10 years of industry experience across a variety of areas, including security, networks, and operations.