Amazon S3 の SSE-C(カスタマー提供キーによるサーバーサイド暗号化)を AWS CLI で試してみた

3種ある Amazon S3 サーバーサイド暗号化のうち、もっとも知名度が低いであろう SSE-C を触ってみました。

SSE-C、知ってはいるけど試したことないな

コンバンハ、千葉(幸)です。

Amazon S3 の サーバーサイド暗号化 には以下の3つのオプションが用意されています。

  • SSE-S3
    • Amazon S3 が管理するキーによるサーバー側の暗号化
  • SSE-KMS
    • AWS Key Management Service に保存されている KMS キーによるサーバー側の暗号化
  • SSE-C
    • お客様が指定したキーによるサーバー側の暗号化

このうち上からふたつはマネジメントコンソールから設定できることもあり、比較的試しやすいです。それに対してみっつめの SSE-C はマネジメントコンソールでは設定できない *1ことに加え、暗号化キーをカスタマー側で用意してあげる必要もあり、少しハードルが高い印象です。

とりあえずどんなものか挙動は確認してみたいけど、そのためにわざわざコードを書いたりはしたくないな……などと思っていたのですが、AWS CLI を使えば比較的かんたんに試せることに気づきました。

AWS CLI で SSE-C を使用する際のコマンド例をご紹介します。

SSE-C を AWS CLI で試してみた まとめ

SSE-C で使用する暗号化キーについて

  • 256 bit (32 byte)であれば適当なものでも使用できる
    • 極端な話aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(a 32文字)でもいい
  • それっぽいキーの生成はopenssl rand -hex 16が便利

aws s3api コマンドで SSE-C 暗号化オブジェクトを操作する場合

  • 指定するオプションは以下
    • #1,#2 のみの場合、#2 は base64 エンコードされていてはいけない
    • #3 を使用する場合、#2 の値は base64 エンコードされている必要あり
    # オプション 必須
    1 --sse-customer-algorithm はい AES256固定
    2 --sse-customer-key はい 生成した暗号化キーを指定
    3 --sse-customer-key-md5 いいえ 暗号化キーの MD5 ダイジェストを指定

aws s3 コマンドで SSE-C 暗号化オブジェクトを操作する場合

  • 指定するオプションは以下
    • #3 相当のオプションはなく、指定する暗号化キーは base64 エンコードしてはならない
    • ※1は SSE-C 暗号化したい場合や暗号化されたものをローカルにダウンロードする際に使用
    • ※2は SSE-C 暗号化されたものを S3 間でコピーする際に使用
    # オプション 必須
    4 --sse-c はい※1 AES256固定
    5 --sse-c-key はい※1 生成した暗号化キーを指定
    6 --sse-c-copy-source はい※2 AES256固定
    7 --sse-c-copy-source-key はい※2 生成した暗号化キーを指定

aws s3api コマンドでの SSE-C の操作

put,head,get の操作に対応する以下コマンドで SSE-C に関するオプションを指定できます。

指定するオプションは、共通して以下です。

# オプション 必須
1 --sse-customer-algorithm はい AES256固定
2 --sse-customer-key はい 生成した暗号化キーを指定
3 --sse-customer-key-md5 いいえ 暗号化キーの MD5 ダイジェストを指定

暗号化指定なしの場合

シンプルにaws s3api put-objectを実行する場合のイメージは以下です。出力先のバケット、キー(オブジェクト名)、アップロードするファイルを指定しています。

% aws s3api put-object\
    --bucket chibayuki-hoge-hoge\
    --key test.txt\
    --body test.txt
{
    "ETag": "\"1a6f8abe44f6b9c5a7c2435c43749dea\"",
    "VersionId": "s7dpRf_kP5j.dpTDkN7Sy64pDjat2A2j"
}

上記の例では暗号化なしでオブジェクトがアップロードされたことを表します。

S3 バケット側でデフォルト暗号化を有効化し暗号化キータイプに SSE-S3 を指定した場合、以下の結果となります。

% aws s3api put-object\
    --bucket chibayuki-hoge-hoge\
    --key test.txt\
    --body test.txt
{
    "ETag": "\"1a6f8abe44f6b9c5a7c2435c43749dea\"",
    "ServerSideEncryption": "AES256",
    "VersionId": "RzZV0uQgLlWtinwtCRxXACMI87hxTAGX"
}

アップロード時の明示的な指定がないため、バケットのデフォルト暗号化設定に従って SSE-S3 で暗号化された、ということです。

SSE-C を指定する場合

SSE-C のオプションを使用するため、暗号化キーを生成します。適当な文字列でも問題ありませんが、今回は opensslコマンドでランダムな文字列を生成します。もちろん他のコマンドでも問題ありません。

% SSE_CUSTOMER_KEY=$(openssl rand -hex 16) # -hex を指定することで16進数形式を、16 を指定することで 32 byte の長さを指定している
% echo $SSE_CUSTOMER_KEY
6af5fa30fc3d6c7d7f227857bd600485

生成した暗号化キーを指定し、aws s3api put-objectを実行します。

% aws s3api put-object\
    --bucket chibayuki-hoge-hoge\
    --key test.txt\
    --body test.txt\
    --sse-customer-algorithm AES256\
    --sse-customer-key $SSE_CUSTOMER_KEY
{
    "ETag": "\"55d3d3cb93bb26d8ef55528be46109f4\"",
    "VersionId": "3lKXp6YvTQBsYQV_LLVpMFFsxCcgko9U",
    "SSECustomerAlgorithm": "AES256",
    "SSECustomerKeyMD5": "jqGJdJeEkh62A0vRiIwv8g=="
}

戻り値に暗号化の種類、暗号化キーの MD5 ダイジェストが含まれています。この MD5 ダイジェストは以下で同じ結果を求められます。

% echo -n $SSE_CUSTOMER_KEY | openssl md5 -binary | base64
jqGJdJeEkh62A0vRiIwv8g==

echo する際に-nオプションを付与して改行コードを取り除かないと結果が異なりますので注意してください。

暗号化キーをファイル指定する場合

先ほどの例では暗号化キーを環境変数に格納して指定しましたが、一度ファイルに保存してから指定することもできます。

% openssl rand -hex 16 | tr -d "\n" > sample.key
% cat sample.key
1a62ef2a074463aa6309a7bef7d1dc4f% #末尾に改行が含まれていない

% aws s3api put-object\
    --bucket chibayuki-hoge-hoge\
    --key test2.txt\
    --body test.txt\
    --sse-customer-algorithm AES256\
    --sse-customer-key file://sample.key
{
    "ETag": "\"2f89e8f3b2a685bb7cfd94995e6253da\"",
    "VersionId": "V9LgJDJHO2VaL4VELgf0vDFZVMDJHaML",
    "SSECustomerAlgorithm": "AES256",
    "SSECustomerKeyMD5": "MWrtsntEaGLSLI7qozzxtA=="
}

ここでも暗号化キーに改行が含まれていると正常に解釈されないため注意してください。

% openssl rand -hex 16 > sample.key
% cat sample.key
a6a69d928b5842df214b2cac79c6b1c4 #末尾に改行が含まれている

% aws s3api put-object\
    --bucket chibayuki-hoge-hoge\
    --key test2.txt\
    --body test.txt\
    --sse-customer-algorithm AES256\
    --sse-customer-key file://sample.key

An error occurred (InvalidArgument) when calling the PutObject operation: The secret key was invalid for the specified algorithm.

MD5 ダイジェストをオプションに含める場合

先ほどのコマンドでは"SSECustomerKeyMD5": "jqGJdJeEkh62A0vRiIwv8g=="のような形で、指定した暗号化キーの MD5 ダイジェストを返してくれていました。

この MD5 ダイジェストをリクエスト時に含めることもできます。その際には暗号化キーを base64 エンコードする必要があります。

まずは環境変数をセットします。

% echo $SSE_CUSTOMER_KEY
6af5fa30fc3d6c7d7f227857bd600485

% SSE_CUSTOMER_KEY_BASE64=$(echo -n $SSE_CUSTOMER_KEY | base64)
% SSE_CUSTOMER_KEY_MD5=$(echo -n $SSE_CUSTOMER_KEY | openssl md5 -binary | base64)

% echo $SSE_CUSTOMER_KEY_BASE64
NmFmNWZhMzBmYzNkNmM3ZDdmMjI3ODU3YmQ2MDA0ODU=
% echo $SSE_CUSTOMER_KEY_MD5
jqGJdJeEkh62A0vRiIwv8g==

上記の環境変数を指定しコマンドを実行します。

% aws s3api put-object\
    --bucket chibayuki-hoge-hoge\
    --key test3.txt\
    --body test.txt\
    --sse-customer-algorithm AES256\
    --sse-customer-key $SSE_CUSTOMER_KEY_BASE64\
    --sse-customer-key-md5 $SSE_CUSTOMER_KEY_MD5
{
    "ETag": "\"afac14aa4f036a3a8adcef071f413877\"",
    "VersionId": "5ZMTCdCxng9kDiUflPOI0DkPyyZz5_gE",
    "SSECustomerAlgorithm": "AES256",
    "SSECustomerKeyMD5": "jqGJdJeEkh62A0vRiIwv8g=="
}

上記の指定であれば正常に実行できますが、以下の組み合わせではエラーになります。

--sse-customer-key-md5オプションなしで暗号化キーをbase64エンコードする

% aws s3api put-object\
    --bucket chibayuki-hoge-hoge\
    --key test3.txt\
    --body test.txt\
    --sse-customer-algorithm AES256\
    --sse-customer-key $SSE_CUSTOMER_KEY_BASE64

An error occurred (InvalidArgument) when calling the PutObject operation: The secret key was invalid for the specified algorithm.

The secret key was invalid for the specified algorithm.

--sse-customer-key-md5オプションありで暗号化キーがbase64エンコードされていない

% aws s3api put-object\
    --bucket chibayuki-hoge-hoge\
    --key test3.txt\
    --body test.txt\
    --sse-customer-algorithm AES256\
    --sse-customer-key $SSE_CUSTOMER_KEY\
    --sse-customer-key-md5 $SSE_CUSTOMER_KEY_MD5

An error occurred (InvalidArgument) when calling the PutObject operation: The calculated MD5 hash of the key did not match the hash that was provided.

The calculated MD5 hash of the key did not match the hash that was provided.

SSE-C 暗号化されたオブジェクトを HEAD,GET する

SSE-C で暗号化されたオブジェクトを HEAD,GET する場合、関連するオプションを指定しないとエラーが発生します。

head-objectのエラー

% aws s3api head-object\
    --bucket chibayuki-hoge-hoge\
    --key test.txt

An error occurred (400) when calling the HeadObject operation: Bad Request

get-objectのエラー

% aws s3api get-object test.txt\
    --bucket chibayuki-hoge-hoge\
    --key test.txt

An error occurred (InvalidRequest) when calling the GetObject operation: The object was stored using a form of Server Side Encryption. The correct parameters must be provided to retrieve the object.

それぞれ SSE-C 関連のオプションを指定することで正常に実行できます。--sse-customer-key-md5の指定が必須ではないことや 組み合わせによる base64 エンコードの要否などは、先ほど見たaws s3api put-objectのそれと変わりありません。

head-objectの例

% aws s3api head-object\
    --bucket chibayuki-hoge-hoge\
    --key test.txt\
    --sse-customer-algorithm AES256\
    --sse-customer-key $SSE_CUSTOMER_KEY
{
    "AcceptRanges": "bytes",
    "LastModified": "2022-08-13T09:58:25+00:00",
    "ContentLength": 17,
    "ETag": "\"2ce5d00bc7f219646d6a8e1b8e253d47\"",
    "VersionId": "fCuHNRZEkrmFbkIAEf0AhL4nT28.A9bN",
    "ContentType": "binary/octet-stream",
    "Metadata": {},
    "SSECustomerAlgorithm": "AES256",
    "SSECustomerKeyMD5": "jqGJdJeEkh62A0vRiIwv8g=="
}

get-objectの例(MD5ダイジェスト指定あり)

% aws s3api get-object test.txt\
    --bucket chibayuki-hoge-hoge\
    --key test.txt\
    --sse-customer-algorithm AES256\
    --sse-customer-key $SSE_CUSTOMER_KEY_BASE64\
    --sse-customer-key-md5 $SSE_CUSTOMER_KEY_MD5
{
    "AcceptRanges": "bytes",
    "LastModified": "2022-08-13T09:58:25+00:00",
    "ContentLength": 17,
    "ETag": "\"2ce5d00bc7f219646d6a8e1b8e253d47\"",
    "VersionId": "fCuHNRZEkrmFbkIAEf0AhL4nT28.A9bN",
    "ContentType": "binary/octet-stream",
    "Metadata": {},
    "SSECustomerAlgorithm": "AES256",
    "SSECustomerKeyMD5": "jqGJdJeEkh62A0vRiIwv8g=="
}

aws s3 コマンドでの SSE-C の操作

cp,mv,syncコマンドで以下のオプションを指定できます。

# オプション 必須
4 --sse-c はい※1 AES256固定
5 --sse-c-key はい※1 生成した暗号化キーを指定
6 --sse-c-copy-source はい※2 AES256固定
7 --sse-c-copy-source-key はい※2 生成した暗号化キーを指定
  • ※1は SSE-C 暗号化したい場合や SSE-C 暗号化されたものをローカルにダウンロードする際に使用
  • ※2は SSE-C 暗号化されたものを S3 間でコピーする際に使用

MD5 ダイジェストを指定するオプションはないため、暗号化キーを base64 エンコードする機会もありません。

sse-c を使用する場合

考え方はaws s3apiコマンドの場合と特に変わりありません。

% aws s3 cp test.txt s3://chibayuki-hoge-hoge\
    --sse-c AES256\
    --sse-c-key $SSE_CUSTOMER_KEY
upload: ./test.txt to s3://chibayuki-hoge-hoge/test.txt

SSE-C 暗号化されたものをダウンロードする時も同じオプションの指定です。

% aws s3 cp s3://chibayuki-hoge-hoge/test.txt .\
    --sse-c AES256\
    --sse-c-key $SSE_CUSTOMER_KEY
download: s3://chibayuki-hoge-hoge/test.txt to ./test.txt

ちなみにダウンロード時に暗号化オプションを省略した場合は以下のエラーが発生します。

% aws s3 cp s3://chibayuki-hoge-hoge/test.txt .
fatal error: An error occurred (400) when calling the HeadObject operation: Bad Request

sse-c-copy を使用する場合

SSE-C 暗号化されたオブジェクトを S3 間でコピーする際に指定が必要になります。(ここでの S3 間とは、S3 バケットをまたがない同一バケット間のコピーも含みます。)

SSE-C で暗号化されたオブジェクトを、暗号化オプション指定なしで「移動(コピー後、元のオブジェクトを削除)」させようとするとエラーになります。

% aws s3 cp test.txt s3://chibayuki-hoge-hoge\
    --sse-c AES256\
    --sse-c-key $SSE_CUSTOMER_KEY
upload: ./test.txt to s3://chibayuki-hoge-hoge/test.txt

% aws s3 mv s3://chibayuki-hoge-hoge/test.txt s3://chibayuki-hoge-hoge/test.txt_copy
fatal error: An error occurred (400) when calling the HeadObject operation: Bad Request

--sse-cオプションを指定してもエラーが出ます。

% aws s3 mv s3://chibayuki-hoge-hoge/test.txt s3://chibayuki-hoge-hoge/test.txt_copy\
    --sse-c AES256\
    --sse-c-key $SSE_CUSTOMER_KEY
move failed: s3://chibayuki-hoge-hoge/test.txt to s3://chibayuki-hoge-hoge/test.txt_copy An error occurred (InvalidRequest) when calling the CopyObject operation: The object was stored using a form of Server Side Encryption. The correct parameters must be provided to retrieve the object.

The object was stored using a form of Server Side Encryption. The correct parameters must be provided to retrieve the object.

--sse-c-copyオプションを指定することで正常に動作します。

% aws s3 mv s3://chibayuki-hoge-hoge/test.txt s3://chibayuki-hoge-hoge/test.txt_copy\
    --sse-c-copy-source AES256\
    --sse-c-copy-source-key $SSE_CUSTOMER_KEY
move: s3://chibayuki-hoge-hoge/test.txt to s3://chibayuki-hoge-hoge/test.txt_copy

ただし、この操作により移動されたオブジェクトは SSE-C 暗号化になっていません。 今回の構成では、S3 バケットのデフォルト暗号化設定に基づきSSE-S3で暗号化されています。

% aws s3api head-object\
    --bucket chibayuki-hoge-hoge\
    --key test.txt_copy
{
    "AcceptRanges": "bytes",
    "LastModified": "2022-08-13T10:44:35+00:00",
    "ContentLength": 17,
    "ETag": "\"1a6f8abe44f6b9c5a7c2435c43749dea\"",
    "VersionId": "wce5y4xnexavkpdg56jPWB5pdfcPTvaP",
    "ContentType": "text/plain",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}

移動後のオブジェクトも SSE-C 暗号化にしたい場合、--sse-cオプションと組み合わせて使用する必要があります。

% aws s3 mv s3://chibayuki-hoge-hoge/test.txt s3://chibayuki-hoge-hoge/test.txt_copy2\
    --sse-c-copy-source AES256\
    --sse-c-copy-source-key $SSE_CUSTOMER_KEY\
    --sse-c AES256\
    --sse-c-key $SSE_CUSTOMER_KEY
move: s3://chibayuki-hoge-hoge/test.txt to s3://chibayuki-hoge-hoge/test.txt_copy2

これにより、移動後も SSE-C 暗号化された状態であることがわかります。

% aws s3api head-object\
    --bucket chibayuki-hoge-hoge\
    --key test.txt_copy2\
    --sse-customer-algorithm AES256\
    --sse-customer-key $SSE_CUSTOMER_KEY
{
    "AcceptRanges": "bytes",
    "LastModified": "2022-08-13T10:49:50+00:00",
    "ContentLength": 17,
    "ETag": "\"1094473dfd2caf1faffffcd1c3f393c0\"",
    "VersionId": "Qgye93GnQax2b0HQcNyIkzXgLSQAg3pJ",
    "ContentType": "text/plain",
    "Metadata": {},
    "SSECustomerAlgorithm": "AES256",
    "SSECustomerKeyMD5": "jqGJdJeEkh62A0vRiIwv8g=="
}

おまけ:リクエストヘッダーの中身

AWS CLI 実行時に--debugオプションを付与することで、HTTPS リクエストのリクエストヘッダーを確認できます。

% aws s3api put-object\
    --bucket chibayuki-hoge-hoge\
    --key test.txt\
    --body test.txt\
    --sse-customer-algorithm AES256\
    --sse-customer-key $SSE_CUSTOMER_KEY\
    --debug
    
...中略...
PUT
/test.txt

content-md5:Gm+KvkT2ucWnwkNcQ3Sd6g==
host:chibayuki-hoge-hoge.s3.ap-northeast-1.amazonaws.com
x-amz-content-sha256:UNSIGNED-PAYLOAD
x-amz-date:20220813T112226Z
x-amz-server-side-encryption-customer-algorithm:AES256
x-amz-server-side-encryption-customer-key:NmFmNWZhMzBmYzNkNmM3ZDdmMjI3ODU3YmQ2MDA0ODU=
x-amz-server-side-encryption-customer-key-md5:jqGJdJeEkh62A0vRiIwv8g==
...

以下ページに記載があるような SSE-C 暗号化に必要なリクエストヘッダーが、AWS CLI により適宜設定されていることがわかります。

--sse-customer-keyで指定した値が base64 エンコードされた上でx-amz-server-side-encryption-customer-keyに設定されていること、--sse-customer-key-md5を指定しなかった場合でもx-amz-server-side-encryption-customer-key-md5が生成されていること、が少し印象的でした。

終わりに

Amazon S3 の SSE-C 暗号化を AWS CLI で試してみました。

暗号化キーをカスタマー側で用意する という部分のイメージがついていなかったのですが、実際に手を動かして理解が進んだ気がします。

どのオブジェクトをどの暗号化キーで暗号化したかのマッピングをするのはカスタマー側の責務なので、SSE-C 暗号化を使用する場合は注意しましょう。使用した暗号化キーが分からなくなると、コピーやダウンロード、中身の参照ができなくなってしまいます。

また、SSE-C 暗号化オブジェクトを操作するためにはリクエストに暗号化キーの情報を含める必要があり、それをマネジメントコンソールで満たすのは難しいために設定・操作ができない、という部分の腹落ちが進みました。

SSE-C ちょっと試してみたいな、という方の参考になれば幸いです。

以上、 チバユキ (@batchicchi) がお送りしました。

参考

脚注

  1. ここでは、オブジェクトのアップロード時やアップロード後の編集時に SSE-C を指定できないこと、バケット設定のデフォルト暗号化のキータイプとして SSE-C を指定できないことを指しています。