S3サーバサイドの暗号化とはなにか

# 概要

- S3のオブジェクトの暗号化といっても、サーバサイド暗号化にはいくつか種類がある。
- ただ、どんな違いがあるのかよくわかっていない。
- 試してみた。

# 対象

- S3バケットのオブジェクトに暗号化をする要件があるが、いまいちサーバサイド暗号化の種類の理解不足なので、ちょっとかじりたい。という方。

# 暗号化の種類

### SSE-S3

- AmazonS3がデータ暗号化キーとマスター暗号化キーを管理
- 暗号化の使用に追加料金はかかりません

### SSE-KMS

- AWSマネージド型キーまたは、カスタマー管理型のキーがありKMSでキーを管理
- AWS Key Management Service の料金が適用

### SSE-C

- ユーザーが暗号化キーを管理

# 暗号化してみる

### バケットを作成

- 非サーバサイド暗号化バケット
- SSE-S3でサーバサイド暗号化を行うバケット
- SSE-KMS（AWSマネージド型キー）でサーバサイド暗号化を行うバケット
- SSE-KMS（カスタマー管理型のキー）でサーバサイド暗号化を行うバケット

作成するテンプレート

```template.yaml
AWSTemplateFormatVersion: '2010-09-09'

Resources:
  NonEncryptedBucket:
    Type: AWS::S3::Bucket
  EncryptedBucketBySSES3:
    Type: AWS::S3::Bucket
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: AES256
  EncryptedBucketBySSEKMS:
    Type: AWS::S3::Bucket
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: aws:kms

  KmsKey:
    Type: AWS::KMS::Key
    Properties:
      Description: 'Key created with S3ServerSideEncryptionDemo template.'
      KeyPolicy:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Principal: "*"
          Action: "kms:*"
          Resource: "*"
  EncryptedBucketByCustomerSSEKMS:
    Type: AWS::S3::Bucket
    Properties:
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            KMSMasterKeyID: !Ref KmsKey
            SSEAlgorithm: aws:kms

Outputs:
  NonEncryptedBucket:
    Value: !Ref NonEncryptedBucket
  EncryptedBucketBySSES3:
    Value: !Ref EncryptedBucketBySSES3
  EncryptedBucketBySSEKMS:
    Value: !Ref EncryptedBucketBySSEKMS
  EncryptedBucketByCustomerSSEKMS:
    Value: !Ref EncryptedBucketByCustomerSSEKMS
```

バケット作成

```sh
aws cloudformation create-stack \
    --stack-name s3-server-side-encryption-demo \
    --template-body file://template.yaml
```

### バケットにファイルをアップロード

**非暗号化バケット**

```sh
NON_ENCRYPTED_BUCKET=$(aws cloudformation describe-stacks \
    --stack-name s3-server-side-encryption-demo \
    --query 'Stacks[].Outputs[?OutputKey==`NonEncryptedBucket`].OutputValue' \
    --output text)
aws s3api put-object \
    --bucket ${NON_ENCRYPTED_BUCKET} \
    --key sample.png \
    --body sample.png
  # {
  #     "ETag": "\"b420fe104fb7b48b5dcd12de92c444b8\""
  # }
```

**SSE-S3暗号化バケット**

暗号化をしないバケットへのアップロードと異なり、レスポンスからサーバサイド暗号化にAES256を使っていることが確認できます。

```sh
SSE_S3_ENCRYTED_BUCKET=$(aws cloudformation describe-stacks \
    --stack-name s3-server-side-encryption-demo \
    --query 'Stacks[].Outputs[?OutputKey==`EncryptedBucketBySSES3`].OutputValue' \
    --output text)
aws s3api put-object \
    --bucket ${SSE_S3_ENCRYTED_BUCKET} \
    --key sample.png \
    --body sample.png
  # {
  #     "ETag": "\"b420fe104fb7b48b5dcd12de92c444b8\"",
  #     "ServerSideEncryption": "AES256"
  # }
```

**SSE-KMS暗号化バケット**

このケースも、レスポンスからサーバサイド暗号化にKMSのキーを使っていることが確認できます。
KMSのキーを使っている場合、キーIDも確認できます。

```sh
SSE_KMS_ENCRYTED_BUCKET=$(aws cloudformation describe-stacks \
    --stack-name s3-server-side-encryption-demo \
    --query 'Stacks[].Outputs[?OutputKey==`EncryptedBucketBySSEKMS`].OutputValue' \
    --output text)
aws s3api put-object \
    --bucket ${SSE_KMS_ENCRYTED_BUCKET} \
    --key sample.png \
    --body sample.png
  # {
  #     "SSEKMSKeyId": "arn:aws:kms:ap-northeast-1:1234567890xx:key/8fa790e9-10f6-4d6c-bca1-d9cb5af6e899",
  #     "ETag": "\"8ef45fd3802ab4bb8a8071d2d8d59294\"",
  #     "ServerSideEncryption": "aws:kms"
  # }
```

**KMSに作成したキーを利用しSSE-KMS暗号化バケット**

ユーザ自身が作成したKMSのキーの場合も同様です。

```sh
CUSTOMER_SSE_KMS_ENCRYTED_BUCKET=$(aws cloudformation describe-stacks \
    --stack-name s3-server-side-encryption-demo \
    --query 'Stacks[].Outputs[?OutputKey==`EncryptedBucketByCustomerSSEKMS`].OutputValue' \
    --output text)
aws s3api put-object \
    --bucket ${CUSTOMER_SSE_KMS_ENCRYTED_BUCKET} \
    --key sample.png \
    --body sample.png
  # {
  #     "SSEKMSKeyId": "arn:aws:kms:ap-northeast-1:1234567890xx:key/a52093d0-b506-4861-87dc-c5cda681c92e",
  #     "ETag": "\"1e6befc82805e250db067fd5c648452f\"",
  #     "ServerSideEncryption": "aws:kms"
  # }
```

### SSE-KMSの確認

SSE-KMSでサーバサイド暗号化したオブジェクトには、**SSEKMSKeyId**が一緒にレスポンスが返ってくることが確認できることは上で述べました。
**SSEKMSKeyId**からKMSのどのキーで暗号化が行われたのかを確認することで、このキーが「AWSマネージド型キー」なのか「カスタマー管理型のキー」なのか確認してみましょう。

**SSE-KMS暗号化バケットで使用されたキー**

```sh
SSEKMS_KEY_ID=$(aws s3api head-object \
    --bucket ${SSE_KMS_ENCRYTED_BUCKET} \
    --key sample.png \
    --query 'SSEKMSKeyId' \
    --output text)
aws kms describe-key --key-id ${SSEKMS_KEY_ID}
  # {
  #     "KeyMetadata": {
  #         "Origin": "AWS_KMS",
  #         "KeyId": "8fa790e9-10f6-4d6c-bca1-d9cb5af6e899",
  #         "Description": "Default master key that protects my S3 objects when no other key is defined",
  #         "KeyManager": "AWS",
  #         "Enabled": true,
  #         "KeyUsage": "ENCRYPT_DECRYPT",
  #         "KeyState": "Enabled",
  #         "CreationDate": "2018-09-05T06:35:48.668000+00:00",
  #         "Arn": "arn:aws:kms:ap-northeast-1:1234567890xx:key/8fa790e9-10f6-4d6c-bca1-d9cb5af6e899",
  #         "AWSAccountId": "1234567890xx"
  #     }
  # }
```

KeyManager : **AWS** となっていることから、AWSマネージド型キーを使っていることが確認できます。

**KMSに作成したキーを利用しSSE-KMS暗号化バケットで使用されたキー**

```sh
CUSTOMER_SSEKMS_KEY_ID=$(aws s3api head-object \
    --bucket ${CUSTOMER_SSE_KMS_ENCRYTED_BUCKET} \
    --key sample.png \
    --query 'SSEKMSKeyId' \
    --output text)
aws kms describe-key --key-id ${CUSTOMER_SSEKMS_KEY_ID}
  # {
  #     "KeyMetadata": {
  #         "Origin": "AWS_KMS",
  #         "KeyId": "a52093d0-b506-4861-87dc-c5cda681c92e",
  #         "Description": "Key created with S3ServerSideEncryptionDemo template.",
  #         "KeyManager": "CUSTOMER",
  #         "Enabled": true,
  #         "KeyUsage": "ENCRYPT_DECRYPT",
  #         "KeyState": "Enabled",
  #         "CreationDate": "2019-11-15T13:51:24.941000+00:00",
  #         "Arn": "arn:aws:kms:ap-northeast-1:1234567890xx:key/a52093d0-b506-4861-87dc-c5cda681c92e",
  #         "AWSAccountId": "1234567890xx"
  #     }
  # }
```

KeyManager : **CUSTOMER** となっていることから、カスタマー管理型のキーを使っていることが確認できます。

これはテンプレートの **AWS::KMS::Key** で作成されたキーです。

通常、KMSによる暗号化を指示した場合、デフォルトでは、KMS（AWS Key Management Service）のAWSマネージド型のキーを利用し暗号化を行うが、ユーザ自身がKMSで鍵を作成し、サーバサイド暗号化のキーに利用することが出来ることも確認できました。

### 非暗号化バケットにサーバサイド暗号化を指定してファイルをアップロード

上記では、バケット自体にサーバサイド暗号化の設定をしているバケットに対して、ファイルをアップロードする動作検証を行いました。
他に、サーバサイド暗号化の設定をしていないバケットに対して、ファイルのアップロード時にサーバサイド暗号化の指定をすることも可能です。

**サーバサイド暗号化にSSE-S3を指定してアップロード**

```sh
aws s3api put-object \
    --bucket ${NON_ENCRYPTED_BUCKET} \
    --key sample_by_SSE-S3.png \
    --body sample.png \
    --server-side-encryption AES256
  # {
  #     "ETag": "\"b420fe104fb7b48b5dcd12de92c444b8\"",
  #     "ServerSideEncryption": "AES256"
  # }
```

**サーバサイド暗号化にSSE-KMSを指定してアップロード**

```sh
aws s3api put-object \
    --bucket ${NON_ENCRYPTED_BUCKET} \
    --key sample_by_SSE-KMS.png \
    --body sample.png \
    --server-side-encryption aws:kms
  # {
  #     "SSEKMSKeyId": "arn:aws:kms:ap-northeast-1:1234567890xx:key/8fa790e9-10f6-4d6c-bca1-d9cb5af6e899",
  #     "ETag": "\"cebd51529c2fcd566e1724e763047c23\"",
  #     "ServerSideEncryption": "aws:kms"
  # }
```

**サーバサイド暗号化にKMSに作成したキーでSSE-KMS暗号化を指定してアップロード**

```sh
aws s3api put-object \
    --bucket ${NON_ENCRYPTED_BUCKET} \
    --key sample_by_CustomerSSE-KMS.png \
    --body sample.png \
    --server-side-encryption aws:kms \
    --ssekms-key-id ${CUSTOMER_SSEKMS_KEY_ID}
  # {
  #     "SSEKMSKeyId": "arn:aws:kms:ap-northeast-1:1234567890xx:key/a52093d0-b506-4861-87dc-c5cda681c92e",
  #     "ETag": "\"e9a3aa7b8af5dd8522edd316c64b46f3\"",
  #     "ServerSideEncryption": "aws:kms"
  # }
```

**ユーザが生成したキーを指定してアップロード(SSE-C)**

また、暗号化キーをユーザ自身で管理し暗号化することも可能です。

```sh
SSE_CUSTOMER_KEY=$(cat /dev/urandom | base64 -i | fold -w 32 | head -n 1)
echo ${SSE_CUSTOMER_KEY}
  # bKNQl8YfSwa7eKpxSpuamVc+90MQ5BHC

aws s3api put-object \
    --bucket ${NON_ENCRYPTED_BUCKET} \
    --key sample_by_SSE-C.png \
    --body sample.png \
    --sse-customer-algorithm AES256 \
    --sse-customer-key ${SSE_CUSTOMER_KEY}
  # {
  #     "SSECustomerKeyMD5": "rgDjryTvO47GvZmftTvtPw==",
  #     "SSECustomerAlgorithm": "AES256",
  #     "ETag": "\"66aae60ae1a0dd574cf119b899be7463\""
  # }
```

### SSM-Cで暗号化したファイルのダウンロード

暗号化キーをユーザ自身で管理する場合、バケットからオブジェクトを取得する際に、他のサーバサード暗号化の取得方法と異なります。

```sh
aws s3api head-object \
    --bucket ${NON_ENCRYPTED_BUCKET} \
    --key sample_by_SSE-C.png
  # 
  # An error occurred (400) when calling the HeadObject operation: Bad Request
```

オブジェクトを取得する場合は、鍵を指定してあげる必要があるので、注意が必要です。

```sh
aws s3api head-object \
    --bucket ${NON_ENCRYPTED_BUCKET} \
    --key sample_by_SSE-C.png \
    --sse-customer-algorithm AES256 \
    --sse-customer-key ${SSE_CUSTOMER_KEY}
  # {
  #     "AcceptRanges": "bytes",
  #     "ContentType": "binary/octet-stream",
  #     "LastModified": "2019-11-15T14:30:41+00:00",
  #     "ContentLength": 178061,
  #     "SSECustomerAlgorithm": "AES256",
  #     "ETag": "\"66aae60ae1a0dd574cf119b899be7463\"",
  #     "SSECustomerKeyMD5": "rgDjryTvO47GvZmftTvtPw==",
  #     "Metadata": {}
  # }
```

# さいごに

一般的にはSSM-S3でサーバサイドの暗号化は事足りるものだと思います（コスト的にも）。
ただ利用シーンや要件によっては、検証したサーバサイド暗号化が必要になるのかと思います。
その際の、参考になれば。

検証したテンプレートのリポジトリは [S3ServerSideEncryptionDemo](https://github.com/ot-nemoto/S3ServerSideEncryptionDemo) になります。