AlibabaCloudのKMS(Key Management Service)サービスを使ってみた

[前回](https://qiita.com/amen/items/354b4de2d5b33b0f7893)はAlibabaCloudのCloudMonitorサービスを使ってみましたが、今回は、AlibabaCloudのKMS(Key Management Service)サービスを操作してみます。
※AlibabaCloudは、AWSと類似するサービスが多いですが、 今回紹介するAlibabaCloudのKMS(Key Management Service)は、AWSのKMS(Key Management Service)と類似するサービスです。
KMS(Key Management Service)サービスについて、各ベンダーのサービス名一覧は下記にまとめました。

|ベンダー  |サービス名  |
|---|---|
|AlibabaCloud  |Key Management Service  |
|AWS  |Key Management Service  |
|GCP  |Cloud Key Management Service  |
|Azure  |Azure Vault |

## Key Management Service(KMS)

KMS(Key Management Service)は、データの保護に使用する暗号鍵の作成、制御および安全な保管ができる使いやすいサービスです。API、SDKまたはコンソールを使用して、Customer Master Key(CMK)の生成およびそれを用いたユーザーの暗号鍵の管理ができます。
KMSを使用して何の問題を解決できますか。

|役割  |問題 |KMSを使用して問題を解決する方法 |
|---|---|---|
|アプリケーション/ウェブサイト開発者  |プログラムは、暗号化のためのキーを使用する必要があります。キーを安全かつ独立して管理する必要があります。アプリケーションがどこにあってもキーに安全にアクセスする必要があります。  | エンベデッド暗号化技術により、ユーザーはKMSにCustomer Master Key（CMK）を格納し、暗号化されたデータキーのみを展開できます。さらに、ユーザーはKMSのAPIを呼び出して、データキーが必要なときにのみ解読することができます。 |
|サービス開発者  |開発者が、ユーザーのキーとデータの安全性については責任を負いたくありません。ユーザーは自分のキーを個人的に管理する必要がありますが、開発者が指定されたキーを使用するのみで、開発に集中することができます。 | エンベロープ暗号化テクノロジとKMSの公開APIに基づいて、サービス開発者は指定されたCMKを使用してデータキーを暗号化/復号化できます。キーの管理は、ユーザー側に委ねます。 |
|セキュリティ責任者 | コンプライアンス要件を満たすためには、会社のキー管理が必要です。責任者は、キーが手順を追って承認され、キーの使用が監査されることを確実にする必要があります。 |KMSは、統合された認証管理のためにRAMに関連付けることができます。 |

今回は、アプリケーション/ウェブサイト開発者の立場で、KMS(Key Management Service)のSDK（Java）を使用して、Customer Master Key(CMK)で、テキスト情報の暗号化と複合処理を検証してみたいです。
## 手順
さて、作業をしていきましょう。
今回の目標は、Customer Master Key(CMK)の作成から、SDKを使用したテキスト情報の暗号化複合の実装まで説明します。
1. Customer Master Key(CMK)の作成
2. AccessKeyの取得
3. 暗号化と複合処理の検証
### 1. Customer Master Key(CMK)の作成
####■CMKの作成
・[KMS コンソール]にログオンします。
・ページの右上隅にある [キーの作成] ボタンをクリックします。
<img width="2778" alt="スクリーンショット 2018-05-12 21.24.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/256080/d52ce679-4378-e6dc-d1a5-e6d0d9e86a7a.png">
・説明などの情報を入力します。
・[OK]をクリックします。
<img width="1212" alt="スクリーンショット 2018-05-12 21.25.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/256080/21ab92fa-6574-e09d-14ea-d89385d0c6ec.png">
※作成されたCMK情報が一覧画面から確認できます。
<img width="1360" alt="スクリーンショット 2018-05-12 21.27.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/256080/8a57a38d-c93c-6ec4-d099-bdef25c8434c.png">

### 2. AccessKeyの取得
####■AccessKeyの取得
・ホームページの上部にある [AccessKeys] ボタンをクリックします。
<img width="2828" alt="スクリーンショット 2018-05-12 21.31.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/256080/3ca16618-ba08-f8bd-77ab-2df89ba4272a.png">
・セキュリティに関するヒントの確認画面の「AccessKeyの管理を続ける」ボタンをクリックします。
<img width="1210" alt="スクリーンショット 2018-05-12 21.32.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/256080/1512d520-3851-cfb8-ccb0-b1f97f8440d7.png">
・Access Key管理ページに遷移されたら、[Access Keyの作成]ボタンをクリックします。
<img width="2820" alt="スクリーンショット 2018-05-12 21.37.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/256080/fca8f607-86ae-4950-9dbe-e293aa582198.png">
・キー作成が完了しましたら、[表示する]リンクをクリックし、表示されているAccess Key IDとAccess Key Secret情報をメモしておきます。※後述の暗号化複合プログラムに利用する必要があります。
<img width="2824" alt="スクリーンショット 2018-05-12 21.37.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/256080/3971f5a7-6baf-99cc-4e39-1ee42410543b.png">
<img width="2828" alt="スクリーンショット 2018-05-12 21.38.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/256080/32bb3e17-8906-69f0-33cc-2f4da54bfe09.png">

### 3. 暗号化と複合処理の検証
####■検証用プロジェクトの作成
・EclipseからMaven Projectを作成します。※作成時の入力情報は、ご参考をください。変更可
　Group Id:com.kms
　Artifact Id:kms-test
・検証で利用する下記の２つのAlibaba CloudのSDKライブラリの依存設定をpomファイルに追加
　aliyun-java-sdk-core
　aliyun-java-sdk-kms
※pomファイルの内容は下記となります。

```xml:pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.kms</groupId>
  <artifactId>kms-test</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>kms-test</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
	<dependency>
	    <groupId>com.aliyun</groupId>
	    <artifactId>aliyun-java-sdk-core</artifactId>
	    <version>3.5.0</version>
	</dependency>
	<dependency>
	    <groupId>com.aliyun</groupId>
	    <artifactId>aliyun-java-sdk-kms</artifactId>
	    <version>2.5.0</version>
	</dependency>
  </dependencies>
</project>
```

####■検証用プログラムの作成
下記の検証フローのプログラムを作成します。
　・全てのCMKのリストアップ
　・CMKの情報取得
　・hello world平文をCMKで暗号化
　・hello world暗号文をCMKで複合

```java:App.java
package com.kms.kms_test;

import java.util.*;
import java.util.List;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.FormatType;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.http.ProtocolType;
//現在の KMS SDK バージョン:2016-01-20
import com.aliyuncs.kms.model.v20160120.DecryptRequest;
import com.aliyuncs.kms.model.v20160120.DecryptResponse;
import com.aliyuncs.kms.model.v20160120.DescribeKeyRequest;
import com.aliyuncs.kms.model.v20160120.DescribeKeyResponse;
import com.aliyuncs.kms.model.v20160120.EncryptRequest;
import com.aliyuncs.kms.model.v20160120.EncryptResponse;
import com.aliyuncs.kms.model.v20160120.GenerateDataKeyRequest;
import com.aliyuncs.kms.model.v20160120.GenerateDataKeyResponse;
import com.aliyuncs.kms.model.v20160120.ListKeysRequest;
import com.aliyuncs.kms.model.v20160120.ListKeysResponse;
import com.aliyuncs.kms.model.v20160120.ListKeysResponse.Key;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
public class App
{
    static DefaultAcsClient kmsClient;
    //Aliyunクライアント作成
    private static DefaultAcsClient kmsClient(String regionId, String accessKeyId, String accessKeySecret) {
        /**
         * Aliyunクライアント作成：
         * RegionId, AccessKeyId, AccessKeySecret を設定
         */
        IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
        DefaultAcsClient client = new DefaultAcsClient(profile);
        return client;
    }
    //CMKの詳細情報を取得
    private static DescribeKeyResponse DescribeKey(String keyId) throws ClientException {
        final DescribeKeyRequest decKeyReq = new DescribeKeyRequest();
        decKeyReq.setProtocol(ProtocolType.HTTPS);
        decKeyReq.setAcceptFormat(FormatType.JSON);
        decKeyReq.setMethod(MethodType.POST);
        decKeyReq.setKeyId(keyId);
        final DescribeKeyResponse decKeyRes = kmsClient.getAcsResponse(decKeyReq);
        return decKeyRes;
    }
    //登録されているCMKの一覧情報を取得
    private static ListKeysResponse ListKey(int pageNumber, int pageSize) throws ClientException {
        final ListKeysRequest listKeysReq = new ListKeysRequest();
        listKeysReq.setProtocol(ProtocolType.HTTPS);
        listKeysReq.setAcceptFormat(FormatType.JSON);
        listKeysReq.setMethod(MethodType.POST);
        listKeysReq.setPageNumber(pageNumber);
        listKeysReq.setPageSize(pageSize);
        final ListKeysResponse listKeysRes = kmsClient.getAcsResponse(listKeysReq);
        return listKeysRes;
    }
    //データキー情報の取得
    private static GenerateDataKeyResponse GenerateDataKey(String keyId, String keyDesc, int numOfBytes) throws ClientException {
        final  GenerateDataKeyRequest genDKReq = new GenerateDataKeyRequest();
        genDKReq.setProtocol(ProtocolType.HTTPS);
        genDKReq.setAcceptFormat(FormatType.JSON);
        genDKReq.setMethod(MethodType.POST);
        /**
         * KMS openAPI ドキュメントに従ってパラメータを設定します。
         * 1.KeyId
         * 2.KeyDescription
         * 3.NumberOfBytes
         */
        genDKReq.setKeySpec(keyDesc);
        genDKReq.setKeyId(keyId);
        genDKReq.setNumberOfBytes(numOfBytes);
        final GenerateDataKeyResponse genDKRes = kmsClient.getAcsResponse(genDKReq);
        return genDKRes;
    }
    //暗号化処理を実施
    private static  EncryptResponse Encrypt(String keyId, String plainText) throws ClientException {
        final EncryptRequest encReq = new EncryptRequest();
        encReq.setProtocol(ProtocolType.HTTPS);
        encReq.setAcceptFormat(FormatType.JSON);
        encReq.setMethod(MethodType.POST);
        encReq.setKeyId(keyId);
        encReq.setPlaintext(plainText);
        final EncryptResponse encResponse = kmsClient.getAcsResponse(encReq);
        return encResponse;
    }
    //複合処理を実施
    private static DecryptResponse Decrypt(String cipherBlob) throws ClientException {
        final DecryptRequest decReq = new DecryptRequest();
        decReq.setProtocol(ProtocolType.HTTPS);
        decReq.setAcceptFormat(FormatType.JSON);
        decReq.setMethod(MethodType.POST);
        decReq.setCiphertextBlob(cipherBlob);
        final DecryptResponse decResponse = kmsClient.getAcsResponse(decReq);
        return decResponse;
    }
    public static void main(String[] args) {
        System.out.println("===========================================");
        System.out.println(" KMS サービスで開始しています");
        System.out.println("===========================================\n");
        /**
         * アクセス情報をセット
         */
        String regionId = "ap-northeast-1";
        String accessKeyId = "★★前の手順でメモしたAccess Key IDを設定★★";
        String accessKeySecret = "★★前の手順でメモしたAccess Key Secretを設定★★";
        kmsClient = kmsClient(regionId, accessKeyId, accessKeySecret);
        String keyId = null;
        String plainText = "hello world";
        String cipherBlob = null;
        /*アカウント中の全てのマスターキーをリストアップ*/
        try {
            final ListKeysResponse listKeysRes = ListKey(1, 100);
            /**
             * レスポンスを解析し、さらに処理する
             */
            System.out.println("TotalCount: " + listKeysRes.getTotalCount());
            System.out.println("PageNumber: " + listKeysRes.getPageNumber());
            System.out.println("PageSize: " + listKeysRes.getPageSize());
            List<Key> keys = listKeysRes.getKeys();
            Iterator<Key> iterator = keys.iterator();
            while (iterator.hasNext()) {
                keyId = iterator.next().getKeyId();
                System.out.println("KeyId: " + keyId);
            }
            System.out.println("===========================================");
            System.out.println("全てのCMKのリストアップ成功！\n");
            System.out.println("===========================================\n");
        } catch (ClientException eResponse) {
            System.out.println("Failed.");
            System.out.println("Error code: " + eResponse.getErrCode());
            System.out.println("Error message: " + eResponse.getErrMsg());
        }
        /*　キーの記述 */
        try {
            final DescribeKeyResponse decKeyRes = DescribeKey(keyId);
            /**
             * レスポンスを解析し、さらに処理する
             */
            System.out.println("DescribeKey Response: ");
            DescribeKeyResponse.KeyMetadata meta = decKeyRes.getKeyMetadata();
            System.out.println("KeyId: " + meta.getKeyId());
            System.out.println("Description: " + meta.getDescription());
            System.out.println("KeyState: " + meta.getKeyState());
            System.out.println("KeyUsage: " + meta.getKeyUsage());
            System.out.println("===========================================");
            System.out.println("CMKの情報取得成功！");
            System.out.println("===========================================\n");
        } catch (ClientException eResponse) {
            System.out.println("Failed.");
            System.out.println("Error code: " + eResponse.getErrCode());
            System.out.println("Error message: " + eResponse.getErrMsg());
        }
        /*データキー作成*/
        /**
         * リスポンスをリクエストして取得する
         */
        try {
            final GenerateDataKeyResponse genDKResponse = GenerateDataKey(keyId, "AES_256", 64);
            /**
             * レスポンスを解析し、さらに処理する
             */
            System.out.println("CiphertextBlob: " + genDKResponse.getCiphertextBlob());
            System.out.println("KeyId: " + genDKResponse.getKeyId());
            System.out.println("Plaintext: " + genDKResponse.getPlaintext());
            System.out.println("===========================================");
            System.out.println("データキー作成成功！");
            System.out.println("===========================================\n");
        } catch (ClientException eResponse) {
            System.out.println("Failed.");
            System.out.println("Error code: " + eResponse.getErrCode());
            System.out.println("Error message: " + eResponse.getErrMsg());
        }
        /**
         * プレーンテキストを暗号化して暗号を取得する
         */
        try {
            EncryptResponse encResponse = Encrypt(keyId, plainText);
            cipherBlob = encResponse.getCiphertextBlob();
            System.out.println("CiphertextBlob: " + cipherBlob);
            System.out.println("KeyId: " + encResponse.getKeyId());
            System.out.println("===========================================");
            System.out.println("平文を暗号化成功！");
            System.out.println("===========================================\n");
        } catch (ClientException eResponse) {
            System.out.println("Failed.");
            System.out.println("Error code: " + eResponse.getErrCode());
            System.out.println("Error message: " + eResponse.getErrMsg());
        }
        /**
         * 暗号テキストを復号化し、元の平文で結果を検証する。
         */
        try {
            DecryptResponse decResponse = Decrypt(cipherBlob);
            System.out.println("Plaintext: " + decResponse.getPlaintext());
            String verifyPlainText = decResponse.getPlaintext();
            int isMatch = verifyPlainText.compareTo(plainText);
            System.out.println("KeyId: " + decResponse.getKeyId());
            System.out.println("===========================================");
            System.out.printf("暗号テキストの復号成功、結果 " + (isMatch == 0 ? "match" + "\n" : "mismatch" + "\n"));
            System.out.println("===========================================\n");
        } catch (ClientException eResponse) {
            System.out.println("Failed.");
            System.out.println("Error code: " + eResponse.getErrCode());
            System.out.println("Error message: " + eResponse.getErrMsg());
        }
    }
}
```

####■検証用プロジェクトのビルドと実行
・Mavenでビルドしたプロジェクトのフォルド構成は、下記となります。※Alibaba CloudのSDKライブラリのバージョンも確認できます。
<img width="1372" alt="スクリーンショット 2018-05-12 22.30.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/256080/9de2c340-a235-d8cb-bfc2-b4dce0b0d552.png">

・プロジェクトを実行します。
　実行結果は下記となります。

```実行結果
===========================================
 KMS サービスで開始しています
===========================================

TotalCount: 3
PageNumber: 1
PageSize: 100
KeyId: 0cc16ce2-a542-4859-a778-3131a2185d79
KeyId: 4f9cb01a-f2b8-49f8-8518-4f0faf99bfa8
KeyId: bb8749d6-06a6-42bd-9724-6811a46c3bb8
===========================================
全てのCMKのリストアップ成功！

===========================================

DescribeKey Response: 
KeyId: bb8749d6-06a6-42bd-9724-6811a46c3bb8
Description: KMSテスト用
KeyState: Enabled
KeyUsage: ENCRYPT/DECRYPT
===========================================
CMKの情報取得成功！
===========================================

CiphertextBlob: NTJiODkwZTMtOTE5My00YTlkLTkxMmYtZDU1YjRkZTMxMTYxTURGUlQvMTFtbTFId1ZOMHFhVzEySWpYVmhjWmRiR0xBQUFBQUFBQUFBQVFsREU2TXV5Ukd3R2FCUWtMSjlHRjUycDh4aTQ3YmxTWDFYR0xjOEd0SWU5cWtsRWdGWm40WGpnK2xlbzVXbXl1WkpHY04wOWJRdUh3dnNZTXFCQUxtY3NLUkdGUTJxdUYzdFF3aXhIYVBzcXMxbmkxVzBEeGFkVGZWcGtXdmZtNDBnN0JFbDNTV3c9PQ==
KeyId: bb8749d6-06a6-42bd-9724-6811a46c3bb8
Plaintext: hlSY2y9iOEWKg39MAC5p6sxm7b3KtKyHchgs2urCG9lG0bEp2p3yKugDUTASbMF6WQhj4dhZUqNeNry0R2FpGg==
===========================================
データキー作成成功！
===========================================

CiphertextBlob: NTJiODkwZTMtOTE5My00YTlkLTkxMmYtZDU1YjRkZTMxMTYxaytYdGpzRS9sK2ZQRjhCM21ad3pZZmlGOHM4NUp3MVpBQUFBQUFBQUFBQXlaa0syTzdocEI2bjJubTFwTWtHVnJLa2NyYnRMa2xCcCs1WT0=
KeyId: bb8749d6-06a6-42bd-9724-6811a46c3bb8
===========================================
平文を暗号化成功！
===========================================

Plaintext: hello world
KeyId: bb8749d6-06a6-42bd-9724-6811a46c3bb8
===========================================
暗号テキストの復号成功、結果 match
===========================================
```

## おわりに

今回は、KMS(Key Management Service)のSDKを使用して、テキスト情報の暗号化と複合処理を検証してみました。
今後は、プログラムに、使用する暗号化キーを、安全かつ独立して管理できるように、KMSに作成管理しましょう。