AWS DynamoDB Client-Side Encryption using Multi-Region Keys in Spring Boot

AWS DynamoDB Client-Side Encryption using Multi-Region Keys in Spring Boot

I recently looked into encrypting AWS DynamoDB Global tables. DynamoDB Global tables are tables with replication enabled to one or more different regions. AWS manages the DynamoDB has server-side encryption enabled by default. Server-side encryption offers Encryption-at-rest. An attacker can still intercept critical data if it is transiting unencrypted to the server. This is where client-side encryption is used in conjunction with server-side encryption to provide an extra layer of security.

Client-Side Encryption

Client-side encryption is a process where data is encrypted on the client application before it is sent to the server. Encrypting the data on the client-side will ensure that the data is protected when making its way to the server, providing us with Encryption-in-transit. There are a few different methods that can be used for client-side encryption, such as public-key cryptography or symmetric key cryptography.

Public key cryptography or Asymmetric-key cryptography uses two different keys in order to encrypt and decrypt data. The first key is known as the public key and can be shared with anyone. The second key is known as the private key and must be kept secret. On the other hand, Symmetric key cryptography uses the same key for both encryption and decryption.

KMS Multi-Region Keys

AWS Key Management Service (KMS) is a key management system that enables you to create and manage keys that are used to encrypt your data. KMS is a multi-region key management system that enables you to create and manage keys in multiple AWS regions. You can use KMS to encrypt data in transit and at rest, and manage access to your keys. KMS is integrated with AWS Identity and Access Management (IAM), so you can control who has access to your keys. KMS is also integrated with AWS CloudTrail, so you can audit who accesses your keys and when.

Setting up Multi-Region Keys for encrypting DynamoDB Global Tables

1. Create a new DynamoDB table in region us-east-1 and create a table replica in region us-west-2. Replicated DynamoDB tables are also known as Global Tables.

AWS DynamoDB Client-Side Encryption using Multi-Region Keys in Spring Boot

Configuring AWS DynamoDB Global tables

2. Now for encrypting data in a global/replicated table we need to make use of Multi-Region Keys. Why? Because we need to be able to encrypt and write to a table in one region and read and decrypt the data in another region. A Multi-Region key is what provides us with this interoperability across regions. Conceptually, it is just a replica of the primary key in a different region with the same underlying cryptographic material used for encryption/decryption of the data.

2.1 First step is to create a primary Multi-region key (MRK). We need to set it up in the us-east-1 region where we originally created our DynamoDB table.

AWS DynamoDB Client-Side Encryption using Multi-Region Keys in Spring Boot

Creating a multi-region primary key in AWS KMS

2.2 Create a Replica of the primary MKR in the us-west-2 region where the DynamoDB table is replicated. This can be configured by going under the Regionality tab on the primary MKR. The replica multi-region key ARN will be more or less the same as the primary key but will only differ in the region value.

AWS DynamoDB Client-Side Encryption using Multi-Region Keys in Spring Boot

Creating a Replica of the primary multi-region key in AMS KMS

3. Now we make changes to the DynamoDB Table Entity in our Spring boot application. This is what our simple entity looked like.

@DynamoDBTable(tableName = "my-test-table")
public class MyEntity {

    @DynamoDBHashKey
    private String id;

    private String country;

    private String user;
}

Before we make any changes to it let's learn more about the Attribute Actions. Attribute Actions tell the item encryptor which actions to perform on each attribute of the item. The attribute action values can be one of the following:

  1. Encrypt and sign – Encrypt the attribute value. (This will happen by default if no annotation is provided). Includes the attribute (name & value) in the item signature.

  2. Sign only – Include the attribute in the item signature. (@DoNotEncrypt)

  3. Do nothing – Do not encrypt or sign the attribute. (@DoNotTouch)

One thing to note is, by default, there is NO encryption of the Hash Key and ideally, it should never be done.

Given the info about the attribute actions, we will add these annotations to our Entity. We add no annotation on the `user` field as we wanted it both Encrypted and Signed. We explicitly set the `country` field to not be encrypted. And by default, `id` is the Hash key that won't be encrypted.

@DynamoDBTable(tableName = "my-test-table")
public class MyEntity {

    @DynamoDBHashKey
    private String id;

    @DoNotTouch
    private String country;

    private String user;
}

4. To integrate the encryption library in our Spring boot app and perform the attribute transformation we need to modify and override the DynamoDBMapper Bean.

@Bean
public AWSKMS amazonKmsClient() {
    return AWSKMSClient.builder().build();
}

/**
     * https://github.com/aws/aws-dynamodb-encryption-java/
     * Important: Use SaveBehavior.PUT or SaveBehavior.CLOBBER with AttributeEncryptor. If you do not do so you risk corrupting your signatures and encrypted data. When PUT or CLOBBER is not specified, fields that are present in the record may not be passed down to the encryptor, which results in fields being left out of the record signature. This in turn can result in records failing to decrypt.
     *
     * @return
     */
@Bean
@Primary
public DynamoDBMapperConfig dynamoDBMapperConfig() {
        return DynamoDBMapperConfig
            .builder()                                                                
                .withSaveBehavior(DynamoDBMapperConfig.SaveBehavior.PUT)
        .build();
    }

@Bean
@Primary
public DynamoDBMapper customDynamoDBMapper(AmazonDynamoDB amazonDynamoDB, AWSKMS kmsClient, DynamoDBMapperConfig dynamoDBMapperConfig) {
        // materialProvider
        final DirectKmsMaterialProvider materialProvider = 
                new DirectKmsMaterialProvider(kmsClient, kmsArn);
        // encryptor
        AttributeTransformer attributeTransformer = 
                new AttributeEncryptor(materialProvider);
        return new DynamoDBMapper(amazonDynamoDB, dynamoDBMapperConfig, attributeTransformer);
    }

5. Finally, this is how you will see the resulting encrypted data in your table.

AWS DynamoDB Client-Side Encryption using Multi-Region Keys in Spring Boot

Encrypted user data in AWS DynamoDB table

And that's it! We are now performing client-side encryption of our data. While it is great to improve the security and privacy in our system, it comes at a cost. Each encryption and decryption on our item brings on the additional overhead of performing these cryptographic operations. Additional complexity also arises when your data model needs to change.