AWS KMSを使ったエンベロープ暗号化を試し、注意点を把握してみる。

# 目的

AWS KMSは暗号化に必要なキーを管理するシステムです。システムの設計をする上でちゃんと理解をしておかないと情報漏洩や重要なデータ喪失につながる危険性があります。
KMSのキーローテーションを実際に使ってみる事で、考慮しなければいけないポイントを把握していきます。またKMSに限らず、暗号化と復号化全般の注意点も把握していきたいと思います。

# 漏洩の種類

要件を把握していないと、意味のない事をしてしまいがちです。どのような場面で漏洩するのかを把握する必要があります。

## データ通信経路

http(s)通信においては通信データは経路上で取得される可能性があります。その為、通信内容自体を暗号化したり（SSL通信）、加えて通信経路を閉じた領域にするなどの対策があります。こちらはKMSにはあまり関係のない領域かと思いますが、把握しておいて損は無いと思います。データ保持には気を付けていても、通信経路が甘かったりすると意味がありません。

## ハードウェア漏洩

HDDやSSDなどは、OS上でファイルを消しても物理上ではデータが残っている場合があります。暗号化していないとそれを参照出来る機材を持っている相手には、単純なテキストとして見えてしまいます。AWSでは物理破棄まで責任をもって行っているとしています。
参考：[wiki：2019年神奈川県HDD転売・情報流出事件](https://ja.wikipedia.org/wiki/2019%E5%B9%B4%E7%A5%9E%E5%A5%88%E5%B7%9D%E7%9C%8CHDD%E8%BB%A2%E5%A3%B2%E3%83%BB%E6%83%85%E5%A0%B1%E6%B5%81%E5%87%BA%E4%BA%8B%E4%BB%B6)
参考：[公式ブログ：クラウドにおける安全なデータの廃棄](https://aws.amazon.com/jp/blogs/news/data_disposal/)

## ファイル漏洩

ちょっと勝手に作らせてもらった造語です。例えば人事部が持っているセンシティブな個人データを何も考えずに社内ネットワークに置くと、普通の社員も参照出来てしまいます。データベースに保持している場合、データベースにアクセス出来なくても、データベースサーバーにアクセス出来る人だと、データベースが使用しているファイルを直接バイナリエディタで見て何かの情報を得る事が出来てしまうケースがあります。
参考：[Oracle公式：透過的データ暗号化の概要](https://docs.oracle.com/cd/E57425_01/121/ASOAG/introduction-to-transparent-data-encryption.htm)
参考：[Oracle Database Security 概要](https://speakerdeck.com/oracle4engineer/oracle-database-security-gai-yao?slide=9)
センシティブなデータに関して、運用者などのファイル自体は見える人はいても中身は解らないなどの対応が必要になると思います。
悪意が無くても共有した圧縮ファイルの中身にセンシティブデータが含まれていたなどの事故が考えられます。そのような事故の時でも暗号化されていれば被害を最小限に留める事が出来ます。

# 暗号化キーに求められること

暗号化キーに関して考察する際に抑えておきたいポイントは以下の通りになると思います。

## 秘匿性

いくら複雑な暗号化しても暗号化キーが漏れてしまえば意味がありません。必要なユーザー以外には参照できない事が重要です。

## 保全性（喪失しない事）

暗号化して悪意のある人が中身を見えなくしても、暗号化キーを喪失してしまうと本来参照したいユーザーも参照できなくなってしまいます。漏洩はしなくても、使えないデータでは意味がありません。暗号化したデータと共に、暗号化（復号化）キーも保持する事が必要です。

## 運用柔軟性

パスワードを定期的に変更する様に、暗号化キーも定期的に変更したいケースがあると思います。
また、暗号化キーが漏れてしまった場合は可能な限り早く暗号化キーを変えなければいけません。漏れてしまったキーで暗号化された既存暗号化データに関しては、新しいキーで再暗号化したり、古い暗号化キーも保持したままデータ側を何等かの別セキュリティ施策が施されたエリアに隔離して必要な時に取り出せるようにする形でしょうか？
要件によりますが、暗号化キーを素早く切り替えられることも重要だと思います。

# 長くなったので先にまとめ

以後長くなるので、今回行った調査や実装を踏まえて感じた注意点をまとめてみます。用語解説などは後述しています。※カスタマー管理キーを使用して、アプリ側でエンベロープ暗号化をする際のケースにおける話です。

- KMSが運用やキー保全を担ってくれるのはルートキー部分のみ。データ暗号化キー関係はアプリ側が管理する必要がある。KMS使っていれば後は考え無しで良いという事は無い（S3などマネージドサービスで使う場合は別）。
- データ暗号化キー（平文）はログ出力無し、ファイルなどへの出力もしないなど秘匿性に気を付ける必要がある。
- データ暗号化キー（暗号化）はバックアップなど暗号化したデータと同レベルで保全性を高めた方がいい。秘匿性はルートキーで暗号化されてる事で担保する。
- どのデータ暗号化キーで暗号化したデータか、という結び付け情報も保持しておかないと復号化出来ないので注意。
- データ暗号化キー（平文）でなく、データ暗号化キー（暗号化）で暗号/復号化してしまうと、データ暗号化キー（暗号化）をデータ暗号化キー（平文）として扱ってる事になる。あくまでもデータ暗号化する時はデータ暗号化キー（平文）を使う。保全性の為にデータ暗号化キー（暗号化）を管理する。
- 同じKMSキーでもデータ暗号化キーは**取得する度に変わる**。後でまたKMSから同じ値を取得、が出来ないので注意！
- 自動ローテーションと手動ローテーションは性質が異なる。データ暗号化キーだけでなく暗号化した時のキーIDも管理して、復号化時に使用する様にすると運用が柔軟になったり安全性が高まりそう。特にマルチテナント別の暗号化キーを使ってる時にはキーIDも保持しておいた方が間違ったテナント情報を復号化する危険性が減りそう。


今回行った過程は以下の図の様になります。

![kms_flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/15a437d5-05c0-2fc1-d62c-f51d05a97e1e.png)

# KMS用語

KMSの資料を読むうえで、用語をちゃんと理解する事が必要です。

## KMSキー

[公式ページ：AWS KMS の概念#AWS KMS keys](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html#kms_keys)
> AWS KMS key は、暗号化キーの論理表現です。KMS キーには、キー ID、キー仕様、キー使用法、作成日、説明、およびキーステータスなどのメタデータが含まれます。最も重要なのは、KMS キーを使用して暗号化オペレーションを実行するときに使用されるキーマテリアルへの参照が含まれていることです。

単純に暗号化キーと言っているものとKMSキーは異なる概念と思った方が良さそうです。`KMSキー`という単語はキーマテリアル及び関連する各種情報を含むという認識が必要そうです。

## キーマテリアル

[公式ページ：AWS KMS の概念#キーマテリアル](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html#key-material)

> キーマテリアルは、暗号化アルゴリズムで使用されるビット単位の文字列です。

こちらの`キーマテリアル`が一般的に使用される暗号化キーと認識してよい様です。

[公式ページ：AWS KMS キーにキーマテリアルをインポートする](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/importing-keys.html)

## エンベロープ暗号化、データキー、ルートキー

エンベロープ暗号化はKMSに限らず暗号化で使用されるソリューションです。KMSはエンベロープ暗号化を基本戦略として考えているので把握する必要があります。その際に出てくる単語がデータキーになります。エンベロープ暗号化におけるルートキーを管理するのがKMSという認識でよさそうです。
反対に、データ暗号化キーはユーザー側が管理する必要があるという意味になります。

[公式ページ：カスタマーマスターキーでエンベロープ暗号化を使用](https://docs.aws.amazon.com/ja_jp/wellarchitected/latest/financial-services-industry-lens/use-envelope-encryption-with-customer-master-keys.html)
> AWS KMS ソリューションは、カスタマーマスターキー (CMK) によるエンベロープ暗号化戦略を使用しています。エンベロープ暗号化は、平文データをデータキーで暗号化し、次にデータキーを別のキーで暗号化する方法です。CMK を使用して、AWS KMS の外部で使用するデータキーを生成、暗号化、復号化して、データを暗号化します。CMKs は AWS KMS で作成され、AWS KMS が暗号化されないままになることはありません。

[公式ページ：AWS KMS の概念#エンベロープ暗号化](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html#enveloping)

> データを暗号化するとデータは保護されますが、暗号化キーを保護する必要があります。1 つの方法としては、それを暗号化します。エンベロープ暗号化は、データキーでプレーンテキストデータを暗号化してから、そのデータキーを別のキーで暗号化する手法です。
> データ暗号化キーを別の暗号化キーで暗号化し、その暗号化キーを別の暗号化キーで暗号化することもできます。しかし、最終的には、キーとデータを復号するために、1 つのキーをプレーンテキストで保持する必要があります。この最上位プレーンテキストキーの暗号化キーは、ルートキーと呼ばれます。

[公式ページ：データキーの暗号化と復号](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/programming-encryption.html)

> 暗号化オペレーションでは AWS KMS keys を使用しますが、4 KB (4,096 バイト) を超えるデータを受け付けることはできません。パスワードや RSA キーなどの少量データを暗号化するためにこれを使用できますが、アプリケーションデータを暗号化するために設計されていません。
> アプリケーションデータを暗号化するには、AWS サービスのサーバー側の暗号化機能、またはクライアント側の暗号化ライブラリを使用します (AWS Encryption SDK や Amazon S3 暗号化クライアントなど)。



# KMSのサービス

AWSコンソールでKMSを開くと以下の４つが表示されます。それぞれ見ていきます。

- AWS マネージド型キー
- カスタマー管理型のキー
- カスタムキーストア（カテゴリ）
  - AWS CloudHSM キーストア
  - 外部キーストア

## AWS マネージド型キー

こちらを選択すると、`aws/s3`、`aws/codecommit`、`aws/sns`、`aws/backup`、`aws/ebs`、`aws/lambda`と、各種AWSサービスで使用される事がイメージできる情報が表示されます。
[公式ページ：AWS KMS の概念#AWS マネージドキー](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html#aws-managed-cmk)

> AWS マネージドキー は、AWS KMS と統合されている AWS のサービスがユーザーに代わって作成、管理、使用する、アカウントの KMS キーです。

とある通り、他の要件が無い場合、AWSサービス上で暗号化を行う場合にAWS マネージド型キーを使う事になると思います。基本的に前述のハードウェア漏洩に対する対策になるかと思います。

## カスタマー管理型のキー

[公式ページ：AWS KMS の概念#カスタマーマネージドキー](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html#customer-cmk)

ユーザーが意識して暗号化を行う時にはこのカスタマー管理型のキーを使う事になると思います。

また、AWSコンソールで、カスタマー管理型のキーの詳細部分では以下の様に表示されます。キーマテリアルの管理をAWSに任せる事も、後述のカスタムキーストアを使用する事も可能という関係性の様です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/fe24533b-9c96-9162-1861-c3ecdf26b6c8.png)

アクセス許可も定義できる形です。キーの削除は、それを使用して暗号化されたデータの削除と同義です。悪意のある削除や事故による削除の保護対策ですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/46011a86-281d-5f91-2d02-f5b893d2bcc8.png)


## カスタムキーストア

前述のカスタマー管理型のキーを使用する際に、キーマテリアルオリジンとして選択する先と思います。
暗号化における一番のポイントであるキーマテリアルの保持をさらに厳格にする為に使用するサービスと考えてよいと思います。単独で使用するのではなく、カスタマー管理型のキーのキーマテリアルの保持先として使用するという事かと思います。

[公式ページ：AWS CloudHSM キーストア](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/keystore-cloudhsm.html)
[公式ページ：外部キーストア](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/keystore-external.html)

## AWS 所有のキー

[公式ページ：AWS KMS の概念#AWS 所有のキー](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html#aws-owned-cmk)

こちらはAWSコンソールには出てきませんが、AWSがサービスを維持する為に使用するものの様です。使用者が意識する必要は無さそうです。


# 実装してみる

やはり実際に試さないと身につきません。以下のポイントを踏まえつつ、実験します。

- KMSのGenerateDataKeyでは、毎回同じデータキーが出来るのか、毎回違うのか
- アプリケーションデータとデータキーの暗号化/復号化はそれぞれどのような処理になるのか
- キーローテーションするとどうなるのか

※キーローテーションには自動と手動があります。自動ではキーIDやキーARNが変わらないのに対し、手動では変わってしまいます。その為にエイリアスを作成します。

[AWS KMS keys ローテーション](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/rotate-keys.html)

## KMSカスタマー管理キーを作る

[公式ページ：aws kms create-key](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/kms/create-key.html)

手動ローテーションを実現する為、２回実行し、２つ作っておきます。以後カスタマー管理キーA,Bとします。
※作った後に、タグを指定した方がいい事に気づきました。
[公式ページ：API オペレーションで KMS キータグを管理する](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/manage-tags-api.html)

```console
$ aws kms create-key
{
    "KeyMetadata": {
        "AWSAccountId": "000000000000",
        "KeyId": "xxxxxxxx-yyyy-zzzz-aaaa-aaaaaaaaaaaa",
        "Arn": "arn:aws:kms:ap-northeast-1:000000000000:key/xxxxxxxx-yyyy-zzzz-aaaa-aaaaaaaaaaaa",
        "CreationDate": "2023-07-02T10:12:52.009000+09:00",
        "Enabled": true,
        "Description": "",
        "KeyUsage": "ENCRYPT_DECRYPT",
        "KeyState": "Enabled",
        "Origin": "AWS_KMS",
        "KeyManager": "CUSTOMER",
        "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
        "KeySpec": "SYMMETRIC_DEFAULT",
        "EncryptionAlgorithms": [
            "SYMMETRIC_DEFAULT"
        ],
        "MultiRegion": false
    }
}
```
## KMSキーエイリアスを作る

[公式ページ：aws kms create-alias](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/kms/create-alias.html)

まずはカスタマー管理キーAのIDを指定してエイリアスを作ります。

```console
$ aws kms create-alias \
    --alias-name alias/envelope-alias \
    --target-key-id xxxxxxxx-yyyy-zzzz-aaaa-aaaaaaaaaaaa
```

## AWSコンソール上で確認

キーが２つ、エイリアスが一つのキーに対して割り当てられています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/93c69f47-6f48-87fe-17a2-befba6be0d7d.png)

IDだけだと役割が解らないので、タグをつけます。

```console:代表してキーA時のコマンド
aws kms tag-resource \
    --key-id xxxxxxxx-yyyy-zzzz-aaaa-aaaaaaaaaaaa \
    --tags TagKey=Group,TagValue=Envelope TagKey=Name,TagValue=KeyA
```

## データ暗号化キー取得

[公式ページ：aws kms generate-data-key](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/kms/generate-data-key.html)

サンプルを真似て作ります。

```bash
aws kms generate-data-key \
    --key-id alias/envelope-alias \
    --key-spec AES_256
```
```console:出力
{
    "CiphertextBlob": "{暗号化データ暗号化キー}",
    "Plaintext": "{平文データ暗号化キー}",
    "KeyId": "arn:aws:kms:ap-northeast-1:000000000000:key/xxxxxxxx-yyyy-zzzz-aaaa-aaaaaaaaaaaa"
}
```

今回はテストの為にそのまま出力しましたが、基本は作成時のコマンドから平文データ暗号化キーと暗号化データ暗号化キーの２種類の情報を取得してそれぞれの処理をする為以下の様になると思います。もしくはsdkを使用してプログラムの中で処理することになると思います。

```bash
DATAKEY_JSON=$(aws kms generate-data-key --key-id alias/envelope-alias --key-spec AES_256)

# 暗号化に使う平文文字列は使い捨ての為、変数に入れるだけ
PLAIN_DATAKEY=$(echo $DATAKEY_JSON | jq -r .Plaintext)
# 暗号化データ暗号化キーは保持の為に、base64デコードした上でファイルへ出力
echo $DATAKEY_JSON | jq -r .CiphertextBlob | base64 --decode > encrypted_datakey
```

後の処理の為に、変数にデータ暗号化キー（平文）を保持し、データ暗号化キー（暗号化）をbase64デコードしてファイル出力しておきます。

```
export PLAIN_DATAKEY_A={データ暗号化キー（平文）}
echo "AQIDA...{データ暗号化キー（暗号化）}" | base64 --decode > encrypted_datakey_A
```


## 実験１：同じKMSキーでデータ暗号化キーを複数回取得した時に内容は変わるのか？

**結果：変わる。２度と同じデータ暗号化キーは手に入らないと考える。**

```console
# １回目
{
    "CiphertextBlob": "AQIDA...{データ暗号化キー（暗号化）}",
    "Plaintext": "7B...{平文データ暗号化キー（平文）}",
    "KeyId": "arn:aws:kms:ap-northeast-1:000000000000:key/xxxxxxxx-yyyy-zzzz-aaaa-aaaaaaaaaaaa"
}

# ２回目
DATAKEY_JSON={
    "CiphertextBlob": "AQIDA...{データ暗号化キー（暗号化）}",
    "Plaintext": "Ci...{データ暗号化キー（平文）}",
    "KeyId": "arn:aws:kms:ap-northeast-1:000000000000:key/xxxxxxxx-yyyy-zzzz-aaaa-aaaaaaaaaaaa"
}
```

## データ暗号化

AWSでもsdkを用意している様ですが、AWSとの役割を明確に分離する為に、シンプルにopensslで暗号化します。

[What is the AWS Encryption SDK?](https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/introduction.html)

```bash
openssl enc -aes-256-cbc -salt -in plain_text_a.txt -out encrypted_text_a.enc -k $PLAIN_DATAKEY_A
```

## データ暗号化キー復号化

想定としては、この時点ではデータ暗号化キー（平文）は保持されていません。データ暗号化キー（暗号化）から復号化する必要があります。

```bash
aws kms decrypt \
    --ciphertext-blob fileb://encrypted_datakey_A \
    --key-id alias/envelope-alias
```

```console:出力
{
    "KeyId": "arn:aws:kms:ap-northeast-1:000000000000:key/xxxxxxxx-yyyy-zzzz-aaaa-aaaaaaaaaaaa",
    "Plaintext": "7B...{データ暗号化キー（平文）}",
    "EncryptionAlgorithm": "SYMMETRIC_DEFAULT"
}
```

データ暗号化キーを取得した時と同じデータ暗号化キー（平文）が取得出来ました。

## 実験２：key-id無指定、間違ったkey-idを指定した時どうなる？

結果：key-id無指定でも復号化出来ました。間違ったkey-idではエラーになりました。

```
aws kms decrypt \
    --ciphertext-blob fileb://encrypted_datakey_A \
    --key-id {カスタマー管理キーBのID}

An error occurred (IncorrectKeyException) when calling the Decrypt operation: The key ID in the request does not identify a CMK that can perform this operation.
```

データ暗号化キー（暗号化）の内部にmetadataが含まれていてどのキーIDで暗号化されたのかを保持してる様です。

[公式ページ：データキーの暗号化と復号#データキーの復号](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/programming-encryption.html#decryption)
> 対称暗号化 KMS キーで復号するときに KeyId パラメータは不要です。AWS KMS は、暗号文 blob 内のメタデータからのデータを暗号化するために使用された KMS キーを取得できます。ただし、ベストプラクティスは常に、使用している KMS キーを指定することです。この方法により、意図した KMS キーを使用することができ、信頼できない KMS キーを使用して暗号文が誤って復号されるのを防ぐことができます。

## データ復号化

元ファイルとは別名で復号化します。

```bash
openssl enc -aes-256-cbc -d -in encrypted_text_a.enc -out plain_text_a_d.txt -k $PLAIN_DATAKEY_A
```

比較しても差分ありませんでした。

```bash
diff plain_text_a.txt plain_text_a_d.txt
```

## キーローテーションする

手動ローテーションはエイリアスの結び付け先を変える事で実現します。自動ローテーションとは性質が違います。自動ローテーションは１年待つ必要があるので、今回は断念します。AWS側でも、手動ローテーションは特殊なコンプライアンスが求められる状況で使用する想定の様です（※もしくは非対称暗号など自動ローテーション対象外）。

[AWS KMS keys ローテーション](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/rotate-keys.html)
> 自動キーローテーションには次の利点があります。
> ・キー ID、キー ARN、リージョン、ポリシー、アクセス許可などの KMS キーのプロパティは、キーがローテーションされても変更されません。
> ・KMS キーのキー ID またはキー ARN を参照するアプリケーションまたはエイリアスを変更する必要はありません。
> ・キーマテリアルのローテーションは、どの AWS のサービスでの KMS キーの使用にも影響しません。
> ・キーローテーションを有効にすると、AWS KMS によって KMS キーが毎年自動的にローテーションされます。更新を覚えている、またはスケジュールする必要はありません。

> KMS キーを手動で更新すると、アプリケーションの KMS キー ID または キーの ARN へのリファレンスも更新する必要があります。KMS キーにわかりやすい名前を関連付けられるエイリアスが、このプロセスを容易にします。エイリアスを使用して、アプリケーションの KMS キーを参照します。


[公式ページ：aws kms update-alias](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/kms/update-alias.html)

カスタマー管理キーBのIDを指定して更新します。
```bash
aws kms update-alias \
    --alias-name alias/envelope-alias \
    --target-key-id 1234abcd-12ab-34cd-56ef-1234567890ab
```

## 実験３：エイリアスが昔指定していたカスタマー管理キーで取得したデータ暗号化キー（暗号化）は、エイリアス指定で復号化出来るか？

```bash
aws kms decrypt \
    --ciphertext-blob fileb://encrypted_datakey_A \
    --key-id alias/envelope-alias
```

**結果：駄目でした。**
※自動ローテーションならキーIDが変わらないはずなので問題無いと思います。手動ローテーションが必要とされるアプリケーションではカスタマー管理キーIDの履歴及び、どのデータ暗号化キーがどのIDのカスタマー管理キーで取得したものかの結び付け情報も確保しておく必要がありそうです。

```console
An error occurred (IncorrectKeyException) when calling the Decrypt operation: The key ID in the request does not identify a CMK that can perform this operation.
```

## 実験４：異なるデータ暗号化キー（平文）で復号化出来るか

**結果：当たり前ですが、駄目でした。**
KMSは関係しない領域です。異なるデータキーで復号化出来るはずがありません。

```
openssl enc -aes-256-cbc -d -in encrypted_text_b.enc -out plain_text_b_d2.txt -k $PLAIN_DATAKEY_A
bad decrypt
40877877717F0000:error:1C800064:Provider routines:ossl_cipher_unpadblock:bad decrypt:../providers/implementations/ciphers/ciphercommon_block.c:124:
```

# 終わりに

最近、AWS Certified Security Specialtyを取得したり、業務でKMSの話が出たりしたのですが、実際に使う上での課題が頭の中であいまいになっていました。今回、自分でローテーションも含めて試した事で具体的な注意点を把握できタと思います。
この記事がどなたかのKMS理解に役立てば幸いです。