AWS SDK for Javaを使ってS3にアップロードするデータを暗号化／SSE-KMS編

**[AWS SDK for JavaでAmazonS3ClientBuilderを使ってS3にデータをアップロード／ダウンロード／一括削除する](http://qiita.com/hmatsu47/items/496a8a49c4bb089f3749)** のアップロードを、サーバーサイド暗号化に対応させる方法です。
結論を先に書くと、**AmazonS3ClientBuilderを使う場合もAmazonS3Clientのときと同じ記述でOK**です。

なお、S3のデータ暗号化の方法には色々な種類がありますが、ここでは「[SSE-KMS](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingKMSEncryption.html)」を取り上げます。

# 0. S3のデータ暗号化の種類について
大きく分けて、サーバー側の暗号化（SSE）とクライアント側の暗号化（CSE）があります。

前者は、クライアントから送信したデータをサーバ側で受け取ってから暗号化してストレージに保存する方法、後者はクライアントから送信する前にデータを暗号化してサーバ側に送信する方法です。
但し、サーバ側の暗号化を使う際にはエンドポイントへの接続にHTTPSを使って通信経路をSSL/TLSで暗号化する必要があるため、いずれにせよ生データがインターネット上を流れるわけではありません。

それぞれ、さらにいくつかの方法に分かれますが、CSEについてはここでは取り上げません。

 * S3で管理された暗号化キーを使うサーバ側暗号化（SSE-S3）
 * AWS KMSで管理された暗号化キーを使うサーバ側暗号化（SSE-KMS）
 * ユーザが用意した暗号化キーを使うサーバ側暗号化（SSE-C）

これらのうち、今回使うSSE-KMSでは、さらに、

 * デフォルトの暗号化キー（無料で自動発行されるキー）を使う方法
 * ユーザが明示的に発行したカスタマーマスターキー（CMK）を使う方法

があります。

# 1. 準備／CMK（キー）の発行とS3バケットへのポリシー設定（必要な場合のみ）
CMKを明示的に発行する場合は、IAMの「暗号化キー」のメニューを使います。
![s3_enc_1.jpg](https://qiita-image-store.s3.amazonaws.com/0/123899/b31d62ee-7d32-30f0-2cb6-d0443f9e0bfa.jpeg)
上の画面で、①（ARN）はバケットポリシー設定の際に、②（キーID）はJavaのコードで暗号化キーIDを指定する際に使います。

なお、S3のアクセスに使うユーザがこのキーを使用できるよう、権限を付与しておくのを忘れないようにしてください。
![s3_enc_2.jpg](https://qiita-image-store.s3.amazonaws.com/0/123899/89e6c0d6-d86e-dce8-8c29-2adf93c32807.jpeg)

CMKを明示的に発行せず、デフォルトの暗号化キーを使う場合は、これらの作業は不要です。

続いて、アップロード時に暗号化を強制したい場合は、バケットポリシーを設定します。
![s3_enc_3.jpg](https://qiita-image-store.s3.amazonaws.com/0/123899/19ae8dd3-5a40-0b2a-afb7-f2bc10672c27.jpeg)
使用する暗号化キーを限定する場合は、13行目末尾の「,」および14行目を記述します。限定しない（暗号化のみ強制する）場合や、デフォルトの暗号化キーを使う場合は、この記述は不要です。

# 2. Javaのコードを変更する
[先の記事](http://qiita.com/hmatsu47/items/496a8a49c4bb089f3749)のサンプルコードを修正します。
なお、「ダウンロード」以降に変更はありません（ダウンロード時には暗号化の指定は不要です）。

```java:S3Access.java（前半部分のみ）
package s3test;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.DeleteObjectsRequest;
import com.amazonaws.services.s3.model.DeleteObjectsRequest.KeyVersion;
import com.amazonaws.services.s3.model.DeleteObjectsResult;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.SSEAwsKeyManagementParams;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;

public class S3Access {

    private static final String ENDPOINT_URL = "https://s3-ap-northeast-1.amazonaws.com";
    private static final String REGION       = "ap-northeast-1";
    private static final String ACCESS_KEY   = "【アクセスキー】";
    private static final String SECRET_KEY   = "【シークレットキー】";
    private static final String KMS_KEY_ID   = "【KMSキーID】※②の部分を入れる";

    //--------------------------------------------------
    // アップロード
    //--------------------------------------------------
    public void putObject(String bucketName, String objectKey, int objectSize, InputStream is) throws Exception {

        // クライアント生成
        AmazonS3 client = getClient(bucketName);

        ObjectMetadata metadata = new ObjectMetadata();
        // 念のためサイズだけセットしておく（不整合ならException）
        metadata.setContentLength(objectSize);

        // アップロード
        PutObjectRequest putRequest = new PutObjectRequest(bucketName, objectKey, is, metadata)
                                    .withSSEAwsKeyManagementParams(new SSEAwsKeyManagementParams(KMS_KEY_ID));
        client.putObject(putRequest);
        client.putObject(bucketName, objectKey, is, metadata);
    }
```

デフォルトの暗号化キーを使う場合は、*PutObjectRequest* のところで**引数のない** *.withSSEAwsKeyManagementParams()* を記述します。

# 3. 注意点
先に軽く説明した暗号化の種類（方法）ごとに、アップロード時リクエストヘッダに記述される内容が異なります。
そのため、**バケットポリシーで暗号化を強制する場合、暗号化の種類に合った記述をする必要があります。**

SSE-S3を使う場合は、以下を参考にしてください。

**[Amazon S3 で管理された暗号化キーによるサーバー側の暗号化 (SSE-S3) を使用したデータの保護](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingServerSideEncryption.html)**
**[AWS SDK for Java を使用したサーバー側の暗号化の指定](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/SSEUsingJavaSDK.html)**

バケットポリシーとJavaコードの対応を間違えると、アップロード時エラーになります。

**SSE-S3を使う方法とSSE-KMSでデフォルトの暗号化キーを使う方法は別物です**ので、混同しないよう注意してください。

**【2017.7.6追記】**
認証にアクセスキーを使うのはセキュアではないので、以下の記事でEC2 IAM Roleを使う形への変更方法を示しています。

**[EC2 IAM Role環境でSSE-KMS暗号化を使ってS3バケットにアクセスする（AWS SDK for Java）](http://qiita.com/hmatsu47/items/ae7a6948b7e486cd8f7b)**