AWS KMSをawscliから試してみる

KMSはAWSの他のサービスを使うときになんとなく設定する程度で、KMSそのものの動きはいままで理解できていませんでした。そこで、awscliで手動で暗号化・復号化の処理をしてみることで、理解を進めました。

# キー作成

`aws kms create-key` コマンドでキーを作成します。

```console
$ aws kms create-key
{
    "KeyMetadata": {
        "AWSAccountId": "123456789012",
        "KeyId": "2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "Arn": "arn:aws:kms:ap-northeast-1:123456789012:key/2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "CreationDate": 1620370256.542,
        "Enabled": true,
        "Description": "",
        "KeyUsage": "ENCRYPT_DECRYPT",
        "KeyState": "Enabled",
        "Origin": "AWS_KMS",
        "KeyManager": "CUSTOMER",
        "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
        "EncryptionAlgorithms": [
            "SYMMETRIC_DEFAULT"
        ]
    }
}
```

ここで作成したキーは、存在は見えるものの、キーそのもの(CMK、Customer Master Key)を見ることはできません。


# AWSアカウントにあるキー一覧を確認

`aws kms list-keys` コマンドでキーの一覧を確認できます。さきほど作成したキーはこの中に含まれています。

```console
$ aws kms list-keys
{
    "Keys": [
        {
            "KeyId": "2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "KeyArn": "arn:aws:kms:ap-northeast-1:123456789012:key/2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        },
        {
            "KeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "KeyArn": "arn:aws:kms:ap-northeast-1:123456789012:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        },
        {
            "KeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "KeyArn": "arn:aws:kms:ap-northeast-1:123456789012:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        },
        ...
    ]
}
```

マネジメントコンソールで見ると、作成したキーはCustomer managed keysに入っていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/ac5e9c64-b937-55f8-7e7c-8416c189a5b9.png)

さきほど作成した以外のキーはAWS managed keysに入っていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/490207/76b110f2-4dcf-c2b6-9603-86c7a5baff3e.png)

エイリアスは `aws kms list-aliases` コマンドなどで確認できます。

```console
$ aws kms list-aliases
{
    "Aliases": [
        {
            "AliasName": "alias/aws/backup",
            "AliasArn": "arn:aws:kms:ap-northeast-1:123456789012:alias/aws/backup",
            "TargetKeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "CreationDate": 1594564129.245,
            "LastUpdatedDate": 1594564129.245
        },
        {
            "AliasName": "alias/aws/dms",
            "AliasArn": "arn:aws:kms:ap-northeast-1:123456789012:alias/aws/dms",
            "TargetKeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "CreationDate": 1598578567.162,
            "LastUpdatedDate": 1598578567.162
        },
        {
            "AliasName": "alias/aws/dynamodb",
            "AliasArn": "arn:aws:kms:ap-northeast-1:123456789012:alias/aws/dynamodb",
            "TargetKeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
            "CreationDate": 1604415194.916,
            "LastUpdatedDate": 1604415194.916
        },
        {
            "AliasName": "alias/aws/ebs",
            "AliasArn": "arn:aws:kms:ap-northeast-1:123456789012:alias/aws/ebs"
        },
        ...
```

# CDK作成

`aws kms generate-data-key` コマンドによりCDKを作成します。CDKはデータを暗号化・復号化するためのキーです。

```console
$ aws kms generate-data-key --key-id 2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --key-spec AES_256
{
    "CiphertextBlob": "AQID...",
    "Plaintext": "zNn6...",
    "KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
```

レスポンスにある、Plaintextはキーそのもので、CiphertextBlobはそのキーを最初に作成したキー(CMK、Customer Master Key)で暗号化したものです。どちらもBASE64でエンコードされた長い文字列です。このあとで試しますが、`aws kms decrypt`コマンドで、CiphertextBlobからPlaintextを復元できます。

CDKは `aws kms generate-data-key` コマンドを実行するごとに異なるキーが返されます。

# データを暗号化

`Hello, World!` というテキストを暗号化してみます。データの暗号化・復号化は、awscliではなく、適当なツールを使えばよいようです。`openssl`を使いました。パスフレーズにさきほど作成したCDKのPlaintextを指定します。BASE64でデコードが必要です。

```console
$ echo 'Hello, World!' | openssl enc -e -aes256 -pbkdf2 -pass file:<(echo 'zNn6...' | base64 -d) | base64
U2FsdGVkX19Qvh8L0t+zpptfNhgHX4a/RzpNB2nql9g=
```

`openssl enc -e`コマンドの `-pass` には、パスフレーズの書かれたファイルを `file:FILEPATH` のようにファイル名で指定します。ここではファイル作成の手間を惜しんで、コマンドの中に `<(...)` で埋め込んでいます。`<(...)` の中身はPlaintextをBASE64でデコードした結果です。

`openssl enc`コマンドの出力は暗号化された結果です。バイナリなので、コンソール上に表示できるようにBASE64でエンコードしています。

# 暗号化したデータを保存

データベースなどにデータを暗号化して保存する際は `U2FsdGVkX19Qvh8L0t+zpptfNhgHX4a/RzpNB2nql9g=` という暗号化したデータとともに、CDKのCiphertextBlobの `AQID....` という文字列（またはそれをBASE64でデコードしたバイナリ）を保存しておきます。CiphertextBlobのから直接データを復号化することはできません。CDKのPlaintextが必要です。Plaintextを得るには、KMSにCiphertextBlobからの復元をリクエストする必要があります。

このあとは、暗号化したデータとCiphertextBlobが保存されていることを想定して、そこからKMSを使って復号化する手順を踏みます。

# CDKのPlaintextを復元

`aws kms decrypt` コマンドによりCDKのCiphertextBlobからPlaintextを復元します。

```console
$ aws kms decrypt --ciphertext-blob fileb://<(echo 'AQID...' | base64 -d)
{
    "KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "Plaintext": "zNn6...",
    "EncryptionAlgorithm": "SYMMETRIC_DEFAULT"
}
```

`--ciphertext-blob` オプションには `fileb://FILEPATH` のように、CiphertextBlobの `AQID....` という文字列をBASE64でデコードしたバイナリを保存したファイル名を指定します。ここではファイル作成の手間を惜しんで、コマンドの中に `<(...)` で埋め込んでいます。`<(...)` の中身はCiphertextBlobをBASE64でデコードした結果です。

レスポンスされたPlaintextが、暗号化したときのPlaintextと一致しています。暗号化したときのパスフレーズを保存しておかなくても、KMSとCiphertextBlobから復元できることがわかります。

KMSはPlaintextを復元する際にはCMKを識別するためのKeyIdが必要なはずですが、 `aws kms decrypt` コマンドではKeyIdの指定が不要です。CiphertextBlobにCMKを識別するための情報が埋め込まれているらしいです。

# データを復号化

`openssl enc -d` で復号化します。無事 `Hello, World!` が復元できました。

```console
$ echo 'U2FsdGVkX19Qvh8L0t+zpptfNhgHX4a/RzpNB2nql9g=' | base64 -d | openssl enc -d -aes256 -pbkdf2 -pass file:<(echo 'zNn6...' | base64 -d)
Hello, World!
```

暗号化したデータは `openssl enc -d`コマンドの標準入力に渡しています。 `U2FsdGVk...` はBASE64でエンコードされた文字列ですので、標準入力前にBASE64でデコードしています。

`openssl enc -d`コマンドの `-pass` には、暗号化したときと同様にコマンドの中に `<(...)` で埋め込んでいます。