How to set up S3-managed server-side encryption

Server-side encryption in S3 can be done in three different ways. The common in these methods is that an unencrypted object will be encrypted by S3 before it gets written to the storage. One of these encryption methods is when S3 manages the encryption keys.

S3 server-side encryption using S3-managed keys (SSE-S3) is probably the easiest way to apply encryption at rest on the objects.

1. About SSE-S3

In this case, S3 encrypts the objects with a key that S3 manages. Each object is encrypted with a unique key and the keys themselves are encrypted with a master key (envelope encryption).

SSE-S3 uses AES-256 block cypher to encrypt objects (but not metadata).

2. Pre-requisites

If you decide to follow along, you’ll need to have an AWS account (free tier available), user credentials for programmatic access to AWS services and the AWS CLI installed on your computer.

I’ll use the command line throughout here (and in other posts) because I like using it and it’s really helpful to learn the most important APIs and their required parameters. This can be very useful when you prepare for an AWS exam. Of course, all of the exercises below can be done in the Console, too.

3. Two ways to use SSE-S3

I’ll present two ways SSE-S3 can be applied to objects. As such, I’ll create two buckets in the us-west-2 region.

One bucket will have a bucket-level setting for SSE-S3, the other one won’t.

In the first case the objects will automatically be encrypted while in the second case I’ll need to explicitly state the encryption option of my choice (SSE-S3) when uploading the object.

4. Scenario 1: Set encryption at the bucket level

4.1. Create the bucket

Let’s create the bucket with the encryption setting. The encryption will affect all objects in the bucket. The name of the bucket can be anything. Mine is called arpadt-encrypted-bucket, because arpadt is my username and the bucket will have the encryption set. You’ll need to use another name because bucket names should be unique across all accounts in the world.

Buckets can easily be created using the following command:

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

Because the bucket is created in a region other than us-east-1, I’ll need to define the LocationConstraint=us-west-2 value for the create-bucket-configuration parameter.

4.2. Set the encryption

I need to define the encryption setting for the bucket. To do this, I created a JSON-file called sse-config.json, which looks like this:

{
  "Rules": [
    {
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "AES256"
      }
    }
  ]
}

I could write this object in the command line but I find it easier to use a file because it’s less likely that I’ll make a typo or miss a bracket.

Add the bucket-level encryption next:

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

Let’s check if the encryption has been successfully set:

aws s3api get-bucket-encryption --bucket arpadt-encrypted-bucket

The response should look like this:

{
  "ServerSideEncryptionConfiguration": {
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "AES256"
        }
      }
    ]
  }
}

It looks OK.

4.3 Upload an object

I created a very simple file called bond.json, which I’ll upload to this bucket. The file will contain - who else - James Bond:

{
  "name": "James Bond"
}

Yeah, it’s super simple.

I can upload it to S3 using the PutObject API:

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

The response in the console should look promising because it contains the ServerSideEncryption property:

{
  "ETag": "\"b93d66acfb0908af3df50fc780dfdfe6\"",
  "ServerSideEncryption": "AES256"
}

What happens is that S3 automatically encrypts any unencrypted objects before they get saved to the disk. There’s no need for bucket policies requiring the objects to contain the x-amz-server-side-encryption header. The encryption setting for the bucket makes every object encrypted at rest.

Let’s see:

aws s3api head-object --bucket arpadt-encrypted-bucket --key bond.json

The HeadObject API request fetches the object metadata without retrieving the object itself.

The response should be similar to this:

{
  "AcceptRanges": "bytes",
  "LastModified": "Sun, 05 Jan 2020 01:06:50 GMT",
  "ContentLength": 27,
  "ETag": "\"b93d66acfb0908af3df50fc780dfdfe6\"",
  "ContentType": "binary/octet-stream",
  "ServerSideEncryption": "AES256", // <-- THIS ONE!
  "Metadata": {}
}

The JSON contains the same ServerSideEncryption property as above.

5. Scenario 2: Add encryption at the object level

I don’t have to set the encryption at the bucket level. It’s possible to encrypt individual objects.

5.1. Create an unencrypted bucket

I’ll again start with creating a bucket:

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

The name of the bucket witnesses my extreme creativity.

This time I won’t call the PutBucketEncryption API.

To double-check that the bucket encryption is not set, I can run the following command:

aws s3api get-bucket-encryption --bucket arpadt-unencrypted-bucket

The response will contain an error:

An error occurred (ServerSideEncryptionConfigurationNotFoundError) when calling the \\
GetBucketEncryption operation: The server side encryption configuration was not found

The error message makes sense because the encryption hasn’t been set at the bucket level.

5.2. Upload the object

James Bond will be uploaded to this bucket, too. But this time I’ll need to define the x-amz-server-side-encryption header, which is featured in every Associate-level AWS certification exam.

The command looks like this:

aws s3api put-object --bucket arpadt-unencrypted-bucket --key bond.json --body bond.json \\
--server-side-encryption AES256

In the case the value of the server-side-encryption parameter will be converted to the value of the x-amz-server-side-encryption header when the API is called. For SSE-S3, the value should be set to AES256.

Similarly, when I use the SDK, I’ll need to define the ServerSideEncryption in the params object, which will be the argument of the putObject method:

const params = {
  // ...
  Bucket: 'arpadt-unencrypted-bucket',
  ServerSideEncryption: 'AES256',
};

The response should be familiar:

{
  "ETag": "\"b93d66acfb0908af3df50fc780dfdfe6\"",
  "ServerSideEncryption": "AES256"
}

The bucket isn’t encrypted but the object is:

aws s3api head-object --bucket arpadt-unencrypted-bucket --key bond.json

The above command will return the following:

{
  "AcceptRanges": "bytes",
  "LastModified": "Sun, 05 Jan 2020 01:32:57 GMT",
  "ContentLength": 27,
  "ETag": "\"b93d66acfb0908af3df50fc780dfdfe6\"",
  "ContentType": "binary/octet-stream",
  "ServerSideEncryption": "AES256",
  "Metadata": {}
}

This is the same response as in the first scenario.

6. Summary

Server-side encryption with S3-managed encryption keys is the simplest and easiest way to protect data at rest in S3.

The bucket itself can be set so that any objects uploaded to the bucket will automatically be encrypted.

If this is not a viable solution, encryption can also be set at the object level by using the x-amz-server-side-encryption: AES256 request header.

Thanks for reading and see you next time.