S3の暗号化方式についておさらい 2021.06

社内向けに書いた記事を転載。

SSE (サーバーサイド暗号化)

SSE-S3

S3が管理するキーでの、透過的暗号化。

  • Pros
    • 設定すればいいだけなのでラク
    • 共通の鍵なので追加コストもなし
  • Cons
    • 透過的復号がされてしまうので、S3のコンソールでダウンロードしても復号されてしまう。クレデンシャル漏洩には無意味。

SSE-S3になっているかは、オブジェクトの「サーバー側の暗号化設定」の箇所で確認できる。

SSE-KMS

AWS-KMSの鍵での、透過的暗号化。

  • Pros
    • 設定すればいいだけなのでラク
  • Cons
    • 透過的復号がされてしまうので、S3のコンソールでダウンロードしても復号されてしまう。クレデンシャル漏洩には無意味。
    • KMSから鍵を取ってくるので回数が増えると多少、コストに響く

確認方法はSSE-S3と同様。

f:id:shrkw:20210615143250p:plain

SSE-C (カスタマーキーによるサーバーサイド暗号化)

ユーザーが管理する鍵をサーバーにオブジェクトと一緒に毎回アップロードしてサーバーサイドで暗号化する方式。

CSEに比べるとあまり変わらないが、暗号化、復号にユーザー側サーバーのリソースを使わないのが利点か。

以下の項目をパラメーターとしてS3にputする。

  • sse_customer_algorithm
    • AES256のみ
  • sse_customer_key
  • sse_customer_key_md5 (optional)
    • なくても使えるけど、つけた方がいい

残念ながらPresigned URLは使えない。発行はできるが、エンドユーザーがアクセスするときにも固有のヘッダーが必要になるため、直接アップロード、ダウンロードなどには使えない。

SSE-C 以外のオブジェクトでは、署名付き URL を生成し、それをブラウザに直接貼り付けることで、たとえばデータにアクセスできます。

ただし、これは SSE-C オブジェクトには当てはまりません。署名付き URL に加えて SSE-C オブジェクトに固有の HTTP ヘッダーも含める必要があります。したがって、SSE-C オブジェクトの署名付き URL はプログラムでのみ使用できます。

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/specifying-s3-c-encryption.html

AWSのサポートにも問い合わせたが、以下を満たす最高のソリューションは存在しないとのこと。

  • 透過的復号をしない
  • 署名付きURLが特別なヘッダーなしで使える

透過的復号に関しては、IAMの権限でGet-Objectを制限することで近い状態にはできるのでは、というアドバイスがあった。

Pros/Cons

  • Pros
    • 透過的復号がされないので、クレデンシャル漏洩に効果がある
  • Cons
    • Presigned URLが実質使えない
    • コンソールから気軽にダウンロードできない
    • させるべきでないならこれもProsになる
  • ローカルで鍵の管理が必要
    • RailsのCredentialsに入れてもいいしKMSから鍵を取得する方式にしてもいいので、それほどのConsでもない

CLIでの例

$ aws s3api put-object --bucket $BUCKET   --key sse-upload   --body sse-c.txt   --sse-customer-algorithm AES256   --sse-customer-key 
{
    "ETag": "\"3a2def088089b2d7d7aee1xxxxxxxxx\"",
    "SSECustomerAlgorithm": "AES256",
    "SSECustomerKeyMD5": "NX6C25NPxF9KJbS4Pxxxxx=="
}
$ aws s3api head-object --bucket $BUCKET   --key sse-upload    --sse-customer-algorithm AES256   --sse-customer-key $KEY
{
    "AcceptRanges": "bytes",
    "LastModified": "2021-05-24T02:03:42+00:00",
    "ContentLength": 11,
    "ETag": "\"3a2def088089b2d7d7aee19ca9bxxxxx\"",
    "ContentType": "binary/octet-stream",
    "Metadata": {},
    "SSECustomerAlgorithm": "AES256",
    "SSECustomerKeyMD5": "NX6C25NPxF9KJbS4Pxxxxx=="
}

S3のコンソールからはサーバー側の暗号化設定、メタデータなどは空欄になっている。

f:id:shrkw:20210615143335p:plain

コンソールからダウンロードしようとすると以下のように拒否される。

<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>AQVE86HB0HCNQW7Z</RequestId>
<HostId>xxxxxxxxxxxxxxx7bXv0ZCp9Ukn4OX710r7UZR/Kl3Dql9cdNr6Tv+rH8EFnHXeqqvejVBK1BthWGo=</HostId>
</Error>

refs.

CSE (クライアントサイド暗号化)

SDKModule: Aws::S3::Encryptionを使うとCSEになる。CSEにも以下の2種類がある。

  • AWS-KMSの鍵を使う
    • 上記SDKのパラメータは kms_key_id, kms_client
  • ローカルの鍵を使う
    • 上記SDKのパラメータは encryption_key
  • 本当はもう一つKeyProviderを渡す形式もあるけど割愛

Pros/Cons

  • Pros
    • 透過的復号がされないので、クレデンシャル漏洩に効果がある
  • Cons
    • 自前のサーバーで一旦、復号しないといけないので、エンドユーザーに渡すようなコンテンツの場合、帯域、リソース、メモリを消費する
    • Presigned URLが使えないので、エンドユーザーからの直接アップロード、ダウンロードが使えない
    • ローカルの鍵の場合、鍵の管理が必要
    • credentials, KMSなど利用するといい

CSEで暗号化されたかどうかはS3上のオブジェクトのメタデータで確認できる。 x-amz-meta-x-amz-keyがあればCSE。暗号化に使われたデータキーがここに収納されている。

CSEJavaと.NETの実装例 クライアント側の暗号化を使用したデータの保護 - Amazon Simple Storage Service