Sitemap
Srineel Mazumdar

Srineel Mazumdar

A curious learner..

Envelope Encryption in AWS — an approach to robust security with better performance

Approach to secure your data using the benefits of both asymmetric (RSA) and symmetric encryption (AES)

10 min readOct 20, 2024

--

Press enter or click to view image in full size
Photo by Markus Spiske on Unsplash

In today’s world there are many applications that are running in internet and requires strict security. Examples are applications for financial companies etc which deal deals with PII, PCI, bank accounts etc. It also spans retail and healthcare industries as well. To protect these data , we need encryption.

Encryption is required both at rest (when data resides in disks/storage) and in-transit (when transferred over network/internet).

Encryption is of 2 types

· Asymmetric: Where a pair of keys (public and private) are used. Public key can be shared with 3rd parties involved in the process and is used to encrypt the data. Private key stays secure with the key owner and is required to decrypt the data encrypted by its corresponding public key. Algorithms like RSA are used to generate key pairs

· Symmetric: Single key is used to encrypt and decrypt data. Algorithms like AES are used for symmetric key generation

Asymmetric key encryption is more secure than symmetric key encryption, however, comes at a performance cost and has limitations on length of data that can be encrypted. To get the best of both worlds, envelope encryption is used.

Envelope encryption uses symmetric keys for encrypting the data to be protected and uses asymmetric keys to protect the symmetric keys that encrypts the data.

Asymmetric keys (RSA)are more secure but slower in performance, whereas symmetric keys (AES) are less secure compared to asymmetric keys but gain in performance during encryption/decryption process.

Here the symmetric keys (AES) are called data keys and asymmetric keys (RSA) are called key encryption keys. I will use DEK (Data Encryption Key) and KEK (Key Encryption Key) going forward. DEK can quickly enable vast amounts of data and public part of KEK can encrypt DEK which is short in length. The encrypted DEKs will be kept with data and transferred with data. The recipient will use the private KEK to decrypt the encrypted DEK and generate plaintext DEK. The plaintext DEK can be used to decrypt the encrypted data. The DEK can be rotated frequently so someone having a copy of the DEK cannot pose bigger harm. However, KEKs, especially the private part, has to be handled with utmost security.

AWS, Azure, GCP and multiple applications/ cloud providers use envelope encryption as this approach is industry standard. The actual implementation varies from application to application and use of programming language.

I will use AWS example to illustrate how it can be implemented.

Key Management Service (KMS) of AWS is a service that is offered for manage and storing cryptographic keys. It has a wide range of features (please refer to documentation). I will focus on the relevant part.

KMS has a maximum limit of encrypting upto 4kb of data for its asymmetric keys, however for symmetric keys it has no such limits. In order to encrypt large amount of data we need to use envelope encryption.

Using operation GenerateDataKey, a pair of plaintext DEK and encrypted DEK is created from KEK in KMS. KMS does not store DEK. The DEK is to be used outside KMS and are of symmetrical in nature. The plaintext DEK will encrypt the data, and post successful encryption gets deleted. The encrypted DEK will reside with data. Once some one wishes to decrypt , they have to decrypt the DEK using KEK and the plaintext DEK obtained with decrypt the data.

GenerateDataKey operation (image source: AWS KMS documentation)

Encryption using Plaintext Data Key (image source: AWS KMS documentation)

Press enter or click to view image in full size

Decryption of encrypted DEK using KEK in KMS (image source: AWS KMS documentation)

Press enter or click to view image in full size

AWS using Envelope encryption in multiple services of its own. Example: in S3, using DEKs objects can be encrypted at rest for security. The usage of DEKs does not increase the cost of KMS and improves performance.

Programmatically its used in AWS Database Encryption SDK ( The AWS Database Encryption SDK is a set of software libraries that enable you to include client-side encryption in your database design. The AWS Database Encryption SDK provides record-level encryption solutions. Reference: What is the AWS Database Encryption SDK? — AWS Database Encryption SDK )

The envelope encryption operations can be done programmatically using AWS Encryption SDK which is available in multiple languages like Python Java etc. and used

To illustrate the use of the mentioned SDK, I will show an example with Java implementation

Steps:

1) I generated asymmetric keys in my own laptop using openssl commands. Keys are generated with. der extension

2) I created KMS asymmetric key pairs in AWS KMS and for key material, I imported my private key from my laptop which I created using openssl commands instead of using KMS key material.

Importing external key material into AWS KMS is slightly elaborate and complex task, however the steps are there to ensure better security for the parties using KMS. In short, before uploading custom key material, the key material itself is encrypted with AWS keys to ensure that they are not compromised during transfer. For better understanding , please refer to Importing key material for AWS KMS keys — AWS Key Management Service (amazon.com) from AWS KMS documentation. To keep the description concise, I am skipping these steps and encouraging readers to follow the steps from documentation. I successfully uploaded my keys into KMS using these steps (although made some mistakes initially !!), so I can say the steps mentioned in documentation are sufficient. Just do not rush and skip anything!

3) Once asymmetric key pairs in AWS KMS are generated, I made a note of key alias and ARN

4) Instead of using the public key of the asymmetric pair from KMS, I copied it from KMS console and stored in AWS Secrets Manager

Reason: To demonstrate the usage of Keyrings in AWS Encryption SDK

5) The private key is still there in my KMS (the pair which I created by importing key material in KMS). I will use it to demonstrate the use of keys stored in KMS to decrypt encrypted text (done by envelope encryption)

In real life, you may share your public key with a 3rd party to encrypt their data. When they send the encrypted data to you along with DEK, you will have to decrypt it using your private key. Theoretically, one can save their private key in AWS Secrets Manager too, but KMS will be the best option. Additionally, if you have create a KMS using imported key material, the private key will always reside in KMS and will never leave KMS unencrypted.

6) Once all the setups are ready, I used AWS Lambda to do my POC as it's easy to setup and run. I chose Java for implementation as the encryption SDK for java has the option to use keyrings

Note: before Java, I tried Python 3.x and faced some issues. In internet, there were multiple documentation mentioning python sdk for encryption supports RSA Raw Keyrings, but when I downloaded their SDK and used as Lambda Layer, I ran into issues. Upon investigating, I found that the SDK code does not have a implementation for RAW RSA Keyrings in its master branch, however there is a separate branch where the implementation is present.

Reference: https://github.com/aws/aws-encryption-sdk-python

https://github.com/aws/aws-encryption-sdk-python/tree/keyring

This is true at the time of me doing my POC. The SDK may change in future. Always check before using

7) Now the most important part is to create a correct IAM role for the Lambda function and assign the correct permissions.

The Lambda IAM role should have basic permissions for its execution as well as interacting with KMS and Secrets Manager

Example

· “logs:CreateLogStream”,

· “logs:PutLogEvents”

· “secretsmanager:GetSecretValue”,

· “secretsmanager:DescribeSecret”,

· “secretsmanager:ListSecretVersionIds”

· “kms:Decrypt”,

· “kms:ListKeyPolicies”,

· “kms:Encrypt”,

Get Srineel Mazumdar’s stories in your inbox

Join Medium for free to get updates from this writer.

· “kms:GenerateDataKey”,

· “kms:DescribeKey”,

· “kms:ListResourceTags”

· “kms:ListKeys”,

· “kms:ListAliases”

· “secretsmanager:GetSecretValue”,

· “secretsmanager:DescribeSecret”,

· “secretsmanager:ListSecretVersionIds”

8) Let's go to Lambda settings. Since it’s a POC, I chose ARM processing environment for Lambda and kept timeout at 2.5 minutes. Memory is 512 mb. You can choose based on your requirements.

9) Now let's go inside the code. I used a dummy string for encryption which is reasonably long.

10) Fetched the public key string from AWS Secrets Manager.

SecretsManagerClient client = SecretsManagerClient.builder().region(region).build();

logger.log("Get Secret Value Request .................................................... ");
GetSecretValueRequest getSecretValueRequest = GetSecretValueRequest.builder().secretId(secretName).build();

GetSecretValueResponse getSecretValueResponse;
try {
getSecretValueResponse = client.getSecretValue(getSecretValueRequest);

logger.log("Get Secret Value Response " + getSecretValueResponse.secretString());

} catch (Exception e) {
logger.log(e.toString());
throw e;
}

JsonObject jsonObject = JsonParser.parseString(getSecretValueResponse.secretString()).getAsJsonObject();

// printing the values
logger.log(jsonObject.get(secretKey).getAsString());

11) Created a pem key (public) from the string .

String publicKeyPEM = pem.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);

logger.log("Encoded publicKeyPEM ...... " + encoded);

StringWriter publicKeyStringWriter = new StringWriter();
PemWriter publicKeyPemWriter = new PemWriter(publicKeyStringWriter);

try {
logger.log("Public Key Encoded ....... " + encoded);
publicKeyPemWriter.writeObject(new PemObject("PUBLIC KEY", encoded));
publicKeyPemWriter.close();
} catch (Exception e) {
throw new RuntimeException("Exception while writing public key PEM", e);
}

return StandardCharsets.UTF_8.encode(publicKeyStringWriter.toString());

12) Create a KMS RSA Key ring using the public key created from Secrets Manager and private key from KMS

 final CreateAwsKmsRsaKeyringInput kmsKeyRing = CreateAwsKmsRsaKeyringInput.builder()
.kmsClient(KmsClient.create()).kmsKeyId(rsaKeyArn).publicKey(publicKeyByteBuffer)
.encryptionAlgorithm(EncryptionAlgorithmSpec.RSAES_OAEP_SHA_256).build();

final MaterialProviders matProv = MaterialProviders.builder()
.MaterialProvidersConfig(MaterialProvidersConfig.builder().build()).build();

IKeyring awsKmsRsaKeyring = matProv.CreateAwsKmsRsaKeyring(kmsKeyRing);

rsaKeyArn — ARN of the key pair I created in KMS by importing key material externally. This will have the private key of KEK to decrypt DEK

publicKeyByteBuffer — the public key part of KEK need to encrypt DEK

encryption algorithm: The RSA algorithm used for encrypting/decrypting DEKs

13) The subsequent code snippets will result in envelope encryption.

String ciphertext = encryptMessage(plaintext, awsKmsRsaKeyring);
String decryptedtext = decryptMessage(ciphertext, awsKmsRsaKeyring);

14) Let us understand how encryption works

final AwsCrypto crypto = AwsCrypto.builder().withCommitmentPolicy(CommitmentPolicy.ForbidEncryptAllowDecrypt)
.withEncryptionAlgorithm(CryptoAlgorithm. ALG_AES_256_GCM_IV12_TAG16_NO_KDF).build();

logger.log(
"Aws Crypto - Encrypt .................................................................................. ");
// Create an encryption context
final Map<String, String> encryptionContext = Collections.singletonMap("ExampleContextKey",
"ExampleContextValue");

// Encrypt the data
final CryptoResult<byte[], ?> encryptResult = crypto.encryptData(keyring,
plaintext.getBytes(StandardCharsets.UTF_8), encryptionContext);

logger.log("Crypto Result post encryption..... " + encryptResult.getResult());

return Base64.getEncoder().encodeToString(encryptResult.getResult());

AwsCrypto : Entry point for encryption SDK

CommitmentPolicy : Key commitment assures that the encrypted data always decrypts to the same plaintext. To provide this security property, beginning in version 1.7.x, the AWS Encryption SDK uses new algorithm suites with key commitment.

Reference: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/migrate-commitment-policy.html

Encryption Context: Random key value pairs which are non-secret but ideally should be meaningful to convey some business information.

Reference: https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html#encryption-context

Note: For KMSKeyRing in AWS Encryption SDK for java, always use the following algorithms

(these Algorithm Suites are without asymmetric signing)

ALG_AES_128_GCM_IV12_TAG16_NO_KDF(“0x0014”),

ALG_AES_192_GCM_IV12_TAG16_NO_KDF(“0x0046”),

ALG_AES_256_GCM_IV12_TAG16_NO_KDF(“0x0078”),

ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256(“0x0114”),

ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA256(“0x0146”),

ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA256(“0x0178”)

15) In decryption part the following is used

// Instantiate the SDK
final AwsCrypto crypto = AwsCrypto.builder().withCommitmentPolicy(CommitmentPolicy.ForbidEncryptAllowDecrypt)
.withEncryptionAlgorithm(CryptoAlgorithm.ALG_AES_256_GCM_IV12_TAG16_NO_KDF).build();

logger.log("Aws Crypto - Decrypt ...... " + ciphertext);
// Create an encryption context
final Map<String, String> encryptionContext = Collections.singletonMap("ExampleContextKey",
"ExampleContextValue");

// 5. Decrypt the data

// Verify that the encryption context in the result contains the
// encryption context supplied to the encryptData method

final CryptoResult<byte[], ?> decryptResult = crypto.decryptData(keyring,
Base64.getDecoder().decode(ciphertext), encryptionContext);

logger.log("Crypto Result post decryption..... " + decryptResult.getResult());

return new String(decryptResult.getResult());

The code is almost the same as above , with encryptData being replaced by decryptData method

How to Apply

AWS KMS RSA keyring can be used in java programs in case where both parties can access the KMS key.

In case the KMS key is not accessible by either of the parties , in that case using custom keystore and custom key, GenerateDataKeyPair command can be executed to create a new key pair and shared between 2 parties. This helps in preventing the original customers key being shared to outsiders, even though there are ways it can be done securely. These private and public key pairs can be saved in AWS Secrets Manager (although many may object this approach, but these keys are derived and with proper caution can be used for specific use cases by saving it in AWS Secrets Manager. For other scenarios, its always recommended to use AWS KMS and datakeys). The secrets can be used to create RAWRSAkeyring and save the keys for encryption and decryption.

The above implementation is also available in Python, however as of July 2024, Raw RSA Keyrings are not available in Python AWS Encryption SDK.

AWS Encryption SDK is available for both Python and Java.

The example provided in this article provides a glimpse of what can be achieved in AWS Encryption SDK and should be sufficiently strong for most of the use cases from security point of view. One can encrypt strings and send them in REST messages or save in filesystem or database.

There are different others features in SDK and different keyrings which I think can be useful. They are as follows:

Discovery Keyring: No wrapping key ring is mentioned in keyring, and SDK searches for keys in KMS. Cannot be used for encryption, only to be used in decryption.

Multi Keyring: One key in keyring is a generator key which creates the plaintext and encrypted version of datakey . The other keys (child key rings) can each encrypt the plaintext key and has its own version of data key. Alternatively, the data key can be encrypted by all the child key rings (in case the generator key ring is KMS Keyring). Decryption logic depends on the encryption logic used (one/multiple keys used to encrypt data key).

Escrowed Keyring: Both KMS and escrowed RSA keys are used for encryption but any one of them can be used for decryption

Data Key Caching : Using cryptographing material cache or hierarchical key rings the data keys are cached and used without making calls to KMS. This reduces cost of KMS usage and prevents from exceeding usage quota.

Covering all features and use cases for AWS SDK for Encryption within the scope of this article is impossible. I would recommend readers to refer to AWS documentation (https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/introduction.html) for better understanding.

Another interesting read would be: What is the AWS Database Encryption SDK? — AWS Database Encryption SDK. The concepts described uses envelope encryption and is worth a read.

--