S3 server side encryption using KMS managed keys

Server-side encryption with KMS managed master key (SSE-KMS) is a way of encrypting objects at rest in S3. Objects are encrypted in a very similar way to the S3 managed encryption (SSE-S3) but this method gives more flexibility to the developer or administrator.

I wrote about S3-managed server-side encryption last time and mentioned that it’s probably the easiest way to encrypt objects at rest in S3.

1. SSE-KMS

Using dedicated KMS keys provides greater flexibility to manage encryption master keys. It provides the user or administrator the option to centrally manage keys and define policies on how to use them.

Objects are encrypted using unique data keys, which are stored alongside the objects. The data keys are encrypted by the master key. The encryption is performed by S3 but the master key is managed by KMS in this case.

2. Apply SSE-KMS

Similarly to SSE-S3, KMS-managed server-side encryption can be done in two ways.

2.1. Set the encryption at the bucket level

SSE-KMS encryption can be set at the bucket level after the bucket is created.

Let’s create a bucket:

aws s3api create-bucket --bucket arpadt-kms-encrypted --region us-west-2 \\
--create-bucket-configuration LocationConstraint=us-west-2

You need to choose a different bucket name because bucket names are unique. Because my bucket is created in a region other than us-east-1, I’ll need to define the LocationConstraint=us-west-2 (replace the region with your favourite one) for the create-bucket-configuration parameter.

I can add the encryption next:

aws s3api put-bucket-encryption --bucket arpadt-kms-encrypted \\
--server-side-encryption-configuration sse-config-kms.json

I didn’t want to write JSON in the command line, so I created a small file (sse-config-kms.json) and referred to it in the command.

The file describes the type of the server-side encryption:

{
  "Rules": [
    {
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms"
      }
    }
  ]
}

By defining aws:kms as the server side encryption algorithm, this setting uses the default KMS key, which is automatically generated when the first such encryption is initiated and is also free of charge.

The object I’ll upload to S3 contains Mr Bond:

{
  "name": "James Bond"
}

I named the file bond.json but it can be anything and so can be the content of the file.

Let’s upload it:

aws s3api put-object --bucket arpadt-kms-encrypted --key bond.json --body bond.json

Because the bucket has the general encryption setting enabled, the object will automatically be encrypted. There’s no need to specify any headers or bucket policies.

The response to the PutObject API call above confirms that:

{
  "ETag": "\"056a489e1d8429b5b59253b7debd335d\"",
  "ServerSideEncryption": "aws:kms",
  "SSEKMSKeyId": "arn:aws:kms:us-west-2:123456789123:key/1f192011-acf5-4583-9fd8-7a57eda6b813"
}

The SSEKMSKeyId contains the ID of the default master key.

2.2. Object level encryption using KMS-managed CMK

The second way is to not set the encryption at the bucket level but send a header with each API request when the object is uploaded, which defines the type of the encryption (aws:kms) and, optionally, the ID of the key.

First, I’ll use the default, KMS-managed CMK, which was also used above.

Let’s create another bucket:

aws s3api create-bucket --bucket arpadt-kms-encrypted-objects --region us-west-2 \\
--create-bucket-configuration LocationConstraint=us-west-2

This time there’s no encryption set at the bucket level.

If I upload an object and I want it to be encrypted, I’ll have to specify a value (aws:kms) for the server-side-encryption parameter (this will appear as the x-amz-server-side-encryption in the request):

aws s3api put-object --bucket arpadt-kms-encrypted-objects --key bond.json \\
--body to-upload.json --server-side-encryption aws:kms

The response should be the same as above:

{
  "ETag": "\"16e3062ee168d85e3cc69a8e4a4cfb4e\"",
  "ServerSideEncryption": "aws:kms",
  "SSEKMSKeyId": "arn:aws:kms:us-west-2:123456789123:key/1f192011-acf5-4583-9fd8-7a57eda6b813"
}

The ServerSideEncryption property reflects the bucket setting for encryption (KMS-managed CMK) and the SSEKMSKeyId (guess what) returns the ID of the default KMS master key, which is used for encryption. This is because I just defined aws:kms for the encryption type and did not specify any keys.

2.3. Use customer managed master keys

I don’t have to use the default master key for encrypting objects in S3.

By specifying the ID of a custom key (customer managed CMK), a greater flexibility can be achieved. Customer managed CMKs can be rotated, deleted, and IAM policies can be created to manage the access to them.

It comes with a cost though. Custom KMS keys cost $1/month. Here’s a post that explains how to create a key in KMS.

Encrypting objects with customer managed CMKs is very similar to using the default KMS master key. The only difference is that I’ll have to define the ID of the key I generated and want S3 to use for encryption as the value of the ssekms-key-id:

aws s3api put-object --bucket arpadt-kms-encrypted-objects --key bond.json \\
--body to-upload.json --server-side-encryption aws:kms --ssekms-key-id ID_OF_THE_KEY

This way, I can have more flexibility in managing the master key.

If the ssekms-key-id is not provided, S3 will use the default KMS master key for encrypting the data key, just as it was seen above.

3. Summary

With SSE-KMS, the master key, which is used to encrypt the data keys, is managed by KMS. This gives the developer greater flexibility to manage the key.

The aws:kms value needs to be provided for the server-side-encryption parameter. If a key id is not specified, S3 will use the default, AWS managed CMK. It’s possible to use custom KMS keys as well; in this case the API call must contain the ID of the key as the value for the ssekms-key-id parameter.

Thanks for reading and see you next time.