Article summary
AWS provides some great services for managing the encryption of secret data. But like most things with AWS, there’s not really a single, ready-to-go, one-size-fits-all solution. Here, I’ll share a strategy for using AWS Secrets Manager and KMS to encrypt and decrypt secret data using one key per user. The benefit of this strategy is that keys can be deleted when you need to “forget” individual users.
Key Terms and AWS Services
Data Encryption Key (DEK)
A “data encryption key” is a key used to encrypt and decrypt data. You will likely want to manage one DEK per user, and that single DEK can be used for each piece of secret data belonging to the user. This way, the user can be totally “forgotten” by deleting the DEK, without affecting access to other users’ secrets.
AWS Key Management Service (KMS)
AWS Key Management Service provides operations and storage for cryptographic keys. KMS has many capabilities, but here we will only be working with symmetric encryption KMS keys. These KMS keys will themselves be used to create and retrieve the DEKs.
AWS Secrets Manager
AWS Secrets Manager is a generic service for storing and versioning secrets. We will use it specifically for storing encrypted DEKs.
Steps
Configure the KMS Key
Sign into the AWS console and navigate to AWS KMS. Create a customer-managed symmetric KMS key using this guide from AWS Give the key an alias that describes the application and EE it will be used for. The default Key Policy given in the console should work well, but it could also be changed so that the “Allow use of the key” section is slightly more strict.
Additionally, the “Allow key administrators to delete this key” checkbox should be unchecked so that the key is not accidentally deleted. Deleting the key would result in all secret data becoming permanently inaccessible! Whichever role is used by the intake/case manager app corresponding to this KMS Key should be given permission to use the Key for cryptographic operations.
Follow these instructions to enable automatic rotation for the KMS key. Set the key rotation period to 90 days (the minimum). AWS’s documentation states that there’s not usually a benefit to rotating the KMS Key, but it doesn’t hurt either. Automatic rotation will not affect the rest of the process described here.
Configure IAM resources
Your application needs to be able to interact with KMS
First, set up an AWS IAM Policy that allows creating and using DEKs via the KMS Key. It should only allow the Actions “kms:Decrypt”, “kms:GenerateDataKey”, “kms:ReEncryptFrom”, and “kms:ReEncryptTo”.
Next, set up an IAM Policy for managing the relevant Secrets Manager secrets (for DEK’s). This is accomplished by assigning a Resource Tag to each DEK when it is created. AWS provides an example of scoping the
The policy should only allow the Actions “secretsmanager:CreateSecret”, “secretsmanager:GetSecretValue”, and “secretsmanager:DeleteSecret”.
Finally, create an IAM Role and assign to it the policies that were just created for accessing KMS and Secrets Manager. This Role can be assumed by, or assigned to, the service account represented by your application.
Generate a DEK
When new secret data is ingested by your application, your application needs to start by invoking GenerateDataKey
to get a new DEK (official docs here). Provide the user’s primary ID as the EncryptionContext
, and use the KMS Key’s ARN as the KeyId
parameter. “AES_256” is probably an appropriate KeySpec
, but you can look over the other options as well.
Calling GenerateDataKey
will return a result including PlainText
, which is the actual DEK for this user, as well as CipherBlob
which is the encrypted DEK that will get stored in AWS Secrets Manager. You can think of this encrypted DEK as a token that we will send back to KMS to get the actual DEK the next time it’s needed.
Encrypt the secret data using the DEK
The implementation will depend on what language and framework you’re using. This Medium post is a great example for Node.js applications. It uses the createCipheriv
function (see docs here). Using this example, the unencrypted DEK should be passed into the key
argument. That’s the Plaintext
that was returned from GenerateDataKey
, not the CipherBlob
! The plaintext
argument is the secret data to be encrypted. You should end up with the encrypted secret, the Initialization Vector (IV) used for encryption, and an Auth Token generated by the aes-256-gcm
algorithm.
Note: the IV passed into createCipheriv
must be unique in order to keep things secure. Using the same IV for multiple times can leak information about the DEK. However, the IV (and the Auth Token) do not strictly need to be kept secret.
Store the encrypted DEK in Secrets Manager
Store the encrypted DEK by passing it into the SecretString
parameter of the CreateSecret
operation (official docs here). Once again, use the KMS key’s ARN for the KmsKeyId
parameter. For the Name
parameter, use a unique ID that does not reference the user’s own ID. Optionally, include a description but once again do not reference the user whose data is encrypted by the DEK!
Store the encrypted data and the name of the DEK Secret
In your main application database, store the encrypted user data, encryption metadata (IV and Auth Token), and the name of the Secrets Manager secret along with the associated user. In order to keep the user data together, consider storing a compound value such as “{Base64-IV}:{Base64-Auth}:{Base64-EncData}”. This way when you need to decrypt the secret data, the string can easily be split into the IV, Auth Token, and encrypted data.
Decrypt the secret data
To decrypt the user data, we first have to fetch the encrypted DEK from AWS Secrets Manager. This is accomplished with the Decrypt
operation (official docs here). Do not include the VersionId
or VersionStage
, since we are not managing versions.
Next, fetch the decrypted DEK from AWS KMS using the Decrypt
operation (official docs here). For this part, use the encrypted DEK as the CiphertextBlob
and use the same EncryptionContext
as before (the user’s ID). KeyId
is optional but recommended, and it is the ARN of the KMS Key. This operation will return a Plaintext
field in the result – this is the unencrypted DEK.
Decrypt the individual secret fields
Just like the encryption process, this part will depend on your language and framework. The Medium post referenced before uses the createDecipheriv
function from Node.js (official docs here) with the aes-256-gcm
algorithm. It takes in the encrypted user data, the IV, the Auth Token, and the DEK to finally produce the decrypted user data!
Update secret data
The simplest way to handle an update to a user’s secret data is to simply encrypt the new data and overwrite the old encrypted secret. This means retrieving the encrypted DEK from Secrets Manager, decrypting it with KMS, and then using that decrypted DEK to encrypt the new secret data. You can safely re-use the existing DEK for the user. You’ll only need to update the data in your primary application database (nothing in AWS).
Destroying secret data
When you delete a user’s secret data from your application database, you will also need to delete the DEK from Secrets Manager using the Delete
operation (official docs here). This ensures the data will remain inaccessible even if a backup of the application database is compromised. Note that the default RecoverWindowInDays
for a deleted secret is 30 days, but it can be set as low as 7. Alternatively, the ForceDeleteWithoutRecovery
option can be used to forego any recovery period.
Rotations
If your secret user data might be long-lived, the relevant secrets should be rotated to minimize the impact of leaks. Secret user data can be rotated using the same process as handling new data. The only difference is that the DEK is used to encrypt the pre-existing user data instead of new data.
A DEK should be rotated by generating a brand new DEK, encrypting the existing user data with the new DEK, and then deleting the old DEK and writing the newly-encrypted user data and DEK secret name to your application database.
Manual rotation of the KMS key is not necessary because individual DEK’s securely reference which version of the KMS Key they were created with, and KMS transparently uses the correct version when decrypting the DEK.
Caveats
This solution will keep data very secure, but it does have some notable downsides:
- AWS Secrets Manager costs $0.40 per secret. This means if you have thousands of users with encrypted data, the monthly cost will be in the hundreds of dollars!
- Because each piece of data must be decrypted individually and AWS Secrets Manager does not have an operation for fetching multiple secrets (DEK’s) at once, this approach does not provide an efficient way to analyze secret data in aggregate.