S3データ格納時のサーバ側暗号化（SSE-S3、SSE-KMS、SSE-C）と、クライアント側暗号化（CSE-KMS、CSE-C）

S3バケットへのデータ格納時にサーバーサイド暗号化のキーを指定できますが、**SSE-S3**だの**SSE-KMS**だの紛らわしい用語が出てきて混乱したので、整理してみた記事です。

# 1. 用語解説
## SSE
**Server Side Encryption**の略です。暗号化・復号をサーバー側で行う暗号化方式です。

## CSE
**Client Side Encryption**の略です。暗号化・復号をクライアント側で行う暗号化方式です。暗号化・復号のキーには、クライアント側で管理するキーだけでなく、KMSのカスタマー管理キーを使用することもできます。

## CMK
**Customer Master Key**の略です。CDKを暗号化するキーです。CMKは、KMS上では暗号化された状態で管理されています。

CMKには「**カスタマー管理型CMK**」と「**AWS管理型CMK**」の2種類があります（他にも「**AWS所有CMK**」がありますが、AWS内部で管理・使用されるCMKなので本記事では割愛します）。

* カスタマー管理型CMKは、KMSのコンソール画面からユーザーが作成できるCMKです。
* AWS管理型は、エイリアス名が`aws/サービス名`（例「aws/ebs」）の、KMSビルトインのCMKです。

以降で登場するCMKはすべて「カスタマー管理型CMK」を指します。

## CDK
**Customer Data Key**の略です。データの暗号化・復号時に実際に使用するキーです。

後述するCSE-KMSで暗号化キーを取得する際、KMS上でCDKが生成され、クライアント側に「**CMKで暗号化されたCDK**」と「**平文のCDK**」が払い出されます。

* この平文のCDKを使用して、クライアント側でデータを暗号化します。
* 復号時は、暗号化されたCDKをCMKで復号して平文のCDKを取得し、この平文のCDKを使用して復号します）。

クライアント側での具体的な手順は以下です。詳細は、KMSのDeveloper Guideの[Envelope encryption](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#enveloping)を参照して下さい。

 * （暗号化）CMKのARNまたはエイリアスをパラメータに指定して、KMS APIの**GenerateDataKey**を呼び出すと「**CMKで暗号化されたCDK**」と「**平文のCDK**」が取得されます。クライアント側でこの平文のCDKを使ってデータを暗号化した後、「**暗号化されたCDK**」と「**CDKで暗号化されたデータ**」をクライアント側でDB等に保存して管理します。平文のCDKは、データの暗号化後すみやかに破棄することがベストプラクティスとされています。

```:GenerateDataKey
$ aws kms generate-data-key \
  --key-id "arn:aws:kms:ap-northeast-1:123456789012:alias/MyCMK" \
  --key-spec "AES_256" \
  | jq -r '.CiphertextBlob' | base64 --decode >mycdk.bin

{
    "CiphertextBlob": "（CMKで暗号化されたCDKのBase64表現）",
    "Plaintext": "(平文のCDKのBase64表現)",
    "KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/12345678-1234-1234-1234-123456789abcd"
}
```

* （復号）`CipherTextBlob`の値（バイナリデータ）をパラメータに指定してKMS APIの**Decrypt**を呼び出すことで、平文のCDK（`PlainText`の値）を取得します。このCDKを使ってデータを復号します。

```:Decrypt
$ aws kms decrypt --ciphertext-blob "fileb://mycdk.bin"
{
    "KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/12345678-1234-1234-1234-123456789abcd",
    "Plaintext": "（平文のCDKのBase64表現）",
    "EncryptionAlgorithm": "SYMMETRIC_DEFAULT"
}
```

# 2. 暗号化方式
暗号化方式は、「**どこで暗号化・復号するか(サーバー/クライアント)**」と「**暗号化のキーはどこで管理するか(S3/KMS/Client)**」の組合せによって、以下の5パターンに分かれます。

||S3|KMS|Client|
|:--|:--:|:--:|:--:|
|サーバー|SSE-S3|SSE-KMS|SSE-C|
|クライアント|－|CSE-KMS|CSE-C|

以下は、各暗号化方式について**暗号化・復号の場所**と**キーの管理者**を一覧化したものです。

|#|方式|暗号化・復号の場所|経路の暗号化|キーの管理者|デフォルトの暗号化(※1)|
|:--:|:--|:--:|:--:|:--:|:--:|
|1|SSE-S3|サーバー|TLS|S3|○|
|2|SSE-KMS|サーバー|TLS|KMS|○（※2）|
|3|SSE-C（※3）（※4）|サーバー|TLS|クライアント（※5）|×|
|4|CSE-KMS|クライアント|CDKでの暗号化＋TLS|KMS|－|
|5|CSE-C（※6）|クライアント|クライアント暗号化＋TLS|クライアント|－|

※1 … バケットのプロパティの「デフォルトの暗号化」の「暗号化キータイプ」で選択可能かを示します。これはサーバーサイドの暗号化方式に関する設定であるため、クライアントサイドの暗号化方式（CSE-xx）については「－」としています。
※2 … **対称暗号のCMK**のみ指定可能です。非対称暗号（RSA、ECC）のCMKは指定できません。
※3 … 表にある通り、**SSE-C**ではKMSは使用されません。
※4 … **SSE-C**で暗号化されたファイルをマネージメントコンソールからダウンロードしようとすると以下のエラーメッセージが返されます。マネージメントコンソールでは暗号化キーを指定できないためです。

```xml:
<Error>
<Code>InvalidRequest</Code>
<Message>The object was stored using a form of Server Side Encryption. The correct parameters must be provided to retrieve the object.</Message>
<RequestId>XXXXXXXXXXXXXXXX</RequestId>
<HostId>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=</HostId>
</Error>
```

※5 … **SSE-C**では、S3側にはランダムなSALT値が付加されたキーのHMAC値が保存されます。このHMAC値は、クライアントからのリクエストをS3側で検証するために使用されます（参考「
[Protecting data using server-side encryption with customer-provided encryption keys (SSE-C)
](https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html)」）。また、S3バケットでバージョニング有効の場合、**バージョンごとに異なるキー**を指定することができます。
※6 … **CSE-C**は、暗号化・復号にもキーの管理にもAWSは関与しないため、本記事では触れません。

# 3. 暗号化されたオブジェクトの属性値

SSE系の各暗号化方式でS3に格納されたオブジェクトの属性値は、S3 APIの**HeadObject**で参照することができます。以下は、各属性値の値をまとめた表です（「－」はヘッダそのものが存在しないことを示します）。

|#|属性|暗号化なし|SSE-S3|SSE-KMS|SSE-C|
|:--:|:--|:--:|:--:|:--:|:--:|
|1|ServerSideEncryption|－|"AES256" |"aws:kms"|－|
|2|SSEKMSKeyId         |－|－|(CMKのARN) |－|
|3|SSECustomerAlgorithm|－|－|－|"AES256"|
|4|SSECustomerKeyMD5   |－|－|－|(暗号化キーのMD5のBase64表現)|

# 4. S3 APIのサンプル

## 暗号化なし

```:PutObject(暗号化なし)
$ aws s3api put-object --bucket mybucket \
  --key sample_plain.txt \
  --body sample.txt

{
    "ETag": "\"900150983cd24fb0d6963f7d28e17f72\""
}
```

```:HeadObject(暗号化なし)
$ aws s3api head-object --bucket mybucket \
  --key sample_plain.txt

{
    "AcceptRanges": "bytes",
    "LastModified": "2020-12-05T10:38:15+00:00",
    "ContentLength": 3,
    "ETag": "\"900150983cd24fb0d6963f7d28e17f72\"",
    "ContentType": "binary/octet-stream",
    "Metadata": {}
}
```

## SSE-S3

```:PutObject(SSE-S3)
$ aws s3api put-object --bucket mybucket \
  --key sample_SSE-S3.txt \
  --body sample.txt \
  --server-side-encryption AES256

{
    "ETag": "\"900150983cd24fb0d6963f7d28e17f72\"",
    "ServerSideEncryption": "AES256"
}
```

```:HeadObject(SSE-S3)
$ aws s3api head-object --bucket mybucket \
  --key sample_SSE-S3.txt

{
    "AcceptRanges": "bytes",
    "LastModified": "2020-12-05T10:44:43+00:00",
    "ContentLength": 3,
    "ETag": "\"900150983cd24fb0d6963f7d28e17f72\"",
    "ContentType": "binary/octet-stream",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}
```

## SSE-KMS

```:PutObject(SSE-KMS)
$ aws s3api put-object --bucket mybucket \
  --key sample_SSE-KMS.txt \
  --body sample.txt \
  --server-side-encryption aws:kms \
  --ssekms-key-id "arn:aws:kms:ap-northeast-1:123456789012:alias/MyCMK"
{
    "ETag": "\"a14e595d3104cc97d27e40d8a50d87c1\"",
    "ServerSideEncryption": "aws:kms",
    "SSEKMSKeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/12345678-1234-1234-1234-123456789abcd"
}
```

```:HeadObject(SSE-KMS)
$ aws s3api head-object --bucket mybucket \
  --key sample_SSE-KMS.txt

{
    "AcceptRanges": "bytes",
    "LastModified": "2020-12-05T10:47:52+00:00",
    "ContentLength": 3,
    "ETag": "\"a14e595d3104cc97d27e40d8a50d87c1\"",
    "ContentType": "binary/octet-stream",
    "ServerSideEncryption": "aws:kms",
    "Metadata": {},
    "SSEKMSKeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/12345678-1234-1234-1234-123456789abcd"
}
```


## SSE-C

```:PutObject(SSE-C)
$ aws s3api put-object --bucket mybucket \
  --key sample_SSE-C.txt \
  --body sample.txt \
  --sse-customer-algorithm AES256 \
  --sse-customer-key abcdefghijklmnopqrstuvwxyz012345

{
    "ETag": "\"769bb7a6b731cf4780a40986eeb23752\"",
    "SSECustomerAlgorithm": "AES256",
    "SSECustomerKeyMD5": "NX6C25NPxF9KJbS4Pci9GQ=="
}
```

```:HeadObject(SSE-C)
$ aws s3api head-object --bucket mybucket \
  --key sample_SSE-C.txt \
  --sse-customer-algorithm AES256 \
  --sse-customer-key abcdefghijklmnopqrstuvwxyz012345

{
    "AcceptRanges": "bytes",
    "LastModified": "2020-12-05T11:08:16+00:00",
    "ContentLength": 3,
    "ETag": "\"d5c7e37972ae63cf5c57ceb30e26f1e2\"",
    "ContentType": "binary/octet-stream",
    "Metadata": {},
    "SSECustomerAlgorithm": "AES256",
    "SSECustomerKeyMD5": "NX6C25NPxF9KJbS4Pci9GQ=="
}
```