AWS Key Management Serviceでエンベロープ暗号化


## はじめに
AWS Key Management Service （KMS） とは、データの暗号化に使用する暗号化キーを管理するためのマネージドサービスです。 KMS では、エンベロープ暗号化という暗号化方式を用いることでデータをセキュアに取り扱うことができます。

エンベロープ暗号化の Java での実装例について、日本語のドキュメントが少ないためまとめてみました。

## エンベロープ暗号化とは
ざっくり説明すると、

- データ
- データを暗号化するための鍵（データキー）
- 鍵を暗号化するための鍵（マスターキー）

を使い分けることでセキュアにしようよ、と言う仕組みのことを言います。

![e076de0e-e74e-0f63-a339-f6d565b89743.png](https://qiita-image-store.s3.amazonaws.com/0/158574/e8d0eedc-eb97-d646-522c-c1e4855f5181.png)

一般的な暗号化方式では、平文のデータを何らかの鍵を使って暗号化することで保護します。このとき、この鍵がデータとセットで盗まれてしまうと、暗号を解かれてしまい、生のデータを見られてしまいます。そこで、データを暗号化した鍵を、別の鍵でさらに暗号化することでデータをより強固に保護しよう、というのがエンベロープ暗号化の考え方です。

KMS を使ったデータの暗号化は、以下の流れで実現できます。

- KMS のコンソール画面でマスターキーを生成する
- マスターキーからデータキーを生成する
- データキーを使って平文のデータを暗号化する
- データキーを暗号化する
- 暗号化済みのデータキーと暗号化済みのデータを大切に保管する

## 実装例
エンベロープ暗号化の実現方法について、 Java での実装例を以下に示します。

なお、マスターキーは KMS のコンソール画面から事前に生成済み、という前提で話を進めます。マスターキーの生成手順については、[AWS のマニュアル](http://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/service-integration.html)か [Developers.IO](http://dev.classmethod.jp/cloud/aws/cm-advent-calendar-2015-aws-relearning-key-management-service/) 等の記事を参考にしてください。

### 事前準備
KMS の利用にあたっては AWS から SDK が提供されています。ここでは Gradle を介してライブラリを使用する場合の例を示します。 `build.gradle` に以下の dependency を追加してください。

```gradle
dependencies {
    compile "com.amazonaws:aws-java-sdk-kms:1.11.202"
}
```

### 暗号化
まずは、 AWS のアクセスキー、シークレットキーを使用して `AWSKMSClient` のインスタンスを生成します。

```java
BasicAWSCredentials credentials = new BasicAWSCredentials("accessKey...", "SecretKey...");
AWSKMSClient client = (AWSKMSClient) AWSKMSClientBuilder.standard()
    .withRegion("ap-northeast-1")
    .withCredentials(new AWSStaticCredentialsProvider(credentials))
    .build();
```

生成したクライアントを使用して、データキーを生成します。 KeyId に KMS のコンソールから生成したマスターキーの ARN を指定して `GenerateDataKeyRequest` のインスタンスを生成し、 `generateDataKey` することでデータキーを取得できます。

```java
GenerateDataKeyRequest generateDataKeyRequest = new GenerateDataKeyRequest()
    .withKeyId("arn:aws:kms:...")
    .withKeySpec(DataKeySpec.AES_128);
GenerateDataKeyResult dataKeyResult = client.generateDataKey(generateDataKeyRequest);
```

生成したデータキーを使って、平文のデータを暗号化します。ここでは AES を使って暗号化していますが、暗号化方式は任意のものをお使いください。

```java
try {
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE,
                new SecretKeySpec(dataKeyResult.getPlaintext().array(), "AES"));
    String decriptedText = Base64.getEncoder()
        .encodeToString(cipher.doFinal("plainText...".getBytes()));
} catch (Exception e) {
    // do something.
}
```

生成された `decriptedText` と 暗号化済みのデータキー（`dataKeyResult.getCiphertextBlob()` で暗号化済みのものを取得できます）は、 Base64 で encode して、 yaml ファイル等に書き写して保管しておきましょう。

### 復号化
続いて、暗号化済みデータの復号化の手順です。復号化は、暗号化済みのデータキーから `decryptRequest` を生成することで行います。

```java
DecryptRequest decryptRequest = new DecryptRequest()
    .withCiphertextBlob(ByteBuffer.wrap(Base64.getDecoder().decode("decryptedDatakey...".getBytes())));
ByteBuffer plainTextKey = client.decrypt(decryptRequest).getPlaintext();
```

ここまでの手順で、暗号化済みのデータキーを平文のデータキーに戻すことができました。続いて、手に入れたデータキーを用いて暗号化済みのデータを復号化します。

```java
byte[] decodeBase64src = Base64.getDecoder().decode("decryptedText...");
try {
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, plainTextKey);
    String plainText = new String(cipher.doFinal(decodeBase64src));
} catch (Exception e) {
    // do something.
}
```

取り出した平文のデータキー（ `plainTextKey` ）は速やかに破棄しましょう。

```java
plainTextKey.clear();
```

## 参考資料
- [AWS Key Management Service とは](http://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/overview.html)
- [AWS Key Management Service の概念](http://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html)
- [AWS再入門 – Amazon KMS編](http://dev.classmethod.jp/cloud/aws/cm-advent-calendar-2015-aws-relearning-key-management-service/)