AWS KMSとOpenSSLでエンベロープ暗号化

# 目次

- はじめに
- エンベロープ暗号化とは
- AWS KMS, OpenSSLを使う
- CLIで実践
- おわりに

# はじめに
システムを運用するにあたり、「認証情報(いわゆる、パスワード)の管理運用」も必要になります。ソースコードの管理は Git で問題ありませんが、パスワードをそのままGitコミットログに載せてしまうと、セキュリティ上の問題があります。もしパブリックリポジトリに AWS クレデンシャルを Push してしまうと、それを狙って定期スクレイピングしている世界中のエンジニアに抜き取られて、ビットコインのマイニングに利用されてしまうかもしれません。

その一方、パスワードを Git 以外で管理するとしても、その場所自体はセキュアなのか？という問題がつきまといます。完全なスタンドアローン管理では可用性がなくなってしまうため、そもそも平文のままパスワードを管理することは現実的ではありません。

そこで

- **パスワードを暗号化して、その暗号文を Git 管理しよう**

という発想になります。

この「パスワードを暗号化して運用する」ときに役立つのが、AWS KMSやOpenSSL です。

# エンベロープ暗号化とは
AWS KMS は「エンベロープ暗号化」を想定した API を提供しています。

エンベロープ暗号化をざっくりと説明すると、3つの input により暗号化処理を行い、2つの output を得るものです。

input

- データ (Plaintext data)
- データを暗号化する鍵 (Data key)
- 鍵を暗号化する鍵 (Master key)

output

- 「暗号化された」データ (Encrypted data)
- 「暗号化された」データを暗号化する鍵 (Encrypted data key)

![kms_key.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261058/aefde361-309d-d7b3-8c67-1ee854457737.png)

データ(Plaintext data)と鍵(Data Key)だけの場合、暗号文と鍵がセットで盗まれると、その暗号文は解読されてしまいます。そこで、「鍵を暗号化する鍵(Master Key)」を追加することにより、データの保護を強固にしようというのがエンベロープ暗号化です。

しかしながら、「鍵を暗号化する鍵(Master Key)」自体もセキュアに管理しなければならないため、『「鍵を暗号化する鍵」を暗号化する鍵(Master master key?)』も、さらに「(略)(Master master master key)」も必要で...という無限後退が発生してしまいます。

この鍵管理問題に終止符を打つために、Master Keyの管理を担当してくれるのが AWS KMS です。

# AWS KMS, OpenSSLを使う
AWS KMSとOpenSSLにより、データを暗号化する・復号化する流れを見ていきます。

データを暗号化する

1. AWS KMSでCMK(Master Key)を作成する
2. CMKを用いて、DataKey作成APIを実行する
3. `DataKey`と`CMKで暗号化されたDataKey`の2つを取得する
4. OpenSSLでDataKeyにより平文データを暗号化する
5. DataKeyと平文データを削除する

データを復号化する

1. `CMKで暗号化されたDataKey`をAWS KMSに管理されたCMKで復号化して、DataKey'を取得する
2. DataKey'により暗号文を復号化して、平文データを取得する
3. DataKey'を削除する

CMK(Master Key)はAWS側で管理されているため、データの暗号化終了時点で手元に残るのは「暗号化されたデータ」と「暗号化されたDataKey」の2つです。どちらも暗号化されているため、文字列情報としてGit管理できます。

# CLIで実践
環境構築が手軽なLocalStackにて、エンベロープ暗号化を実践してみましょう。

## 動作環境
本記事は、以下の環境にて動作確認しています。

```bash
$  docker compose version
Docker Compose version v2.0.0-beta.6

$ openssl version
LibreSSL 2.8.3

$ aws --version
aws-cli/2.2.22 Python/3.9.6 Darwin/19.6.0 source/x86_64 prompt/off
```

## LocalStackの起動準備
新規に作業ディレクトリを用意して、`docker-compose.yml` に以下内容を記入します。

```docker-compose.yml
version: "3.8"

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
    image: localstack/localstack:0.12.1
    network_mode: bridge
    ports:
      - 127.0.0.1:4566:4566/tcp
    environment:
      - SERVICES=${SERVICES- }
      - DEBUG=${DEBUG- }
      - DATA_DIR=${DATA_DIR- }
      - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- }
      - LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY- }
      - KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- }
      - DOCKER_HOST=unix:///var/run/docker.sock
      - HOST_TMP_FOLDER=${TMPDIR}
    volumes:
      - "${TMPDIR:-/tmp/localstack}:/tmp/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
```

そうしたら、LocalStackを起動してください。

```bash
$ docker compose up -d localstack
```

## データを暗号化する
暗号化したいメッセージを用意します。

```bash
$ echo "こんにちは" > plain_message

$ cat plain_message
こんにちは
```

CMKを新規作成し、実行結果の KeyMetadata.KeyId を環境変数に追加します。

```bash
# CMKを作成
$ aws --endpoint-url http://localhost:4566 kms create-key
{
    "KeyMetadata": {
        "AWSAccountId": "000000000000",
        "KeyId": "3f5e3be9-19df-4ee5-83d9-6564ea7a150c",
        "Arn": "arn:aws:kms:us-east-1:000000000000:key/3f5e3be9-19df-4ee5-83d9-6564ea7a150c",
        "CreationDate": "2021-11-21T12:13:57+09:00",
        "Enabled": true,
        "KeyUsage": "ENCRYPT_DECRYPT",
        "KeyState": "Enabled",
        "Origin": "AWS_KMS",
        "KeyManager": "CUSTOMER",
        "CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
        "EncryptionAlgorithms": [
            "SYMMETRIC_DEFAULT"
        ]
    }
}

# KeyMetadata.KeyIdを環境変数(KEY_ID)に追加する
$ export KEY_ID=3f5e3be9-19df-4ee5-83d9-6564ea7a150c
```

KMSにDataKey作成APIを実行し、DataKey本体と暗号化されたDataKeyを取得します。

```bash
# DataKeyを作成する
$ aws --endpoint-url http://localhost:4566 kms generate-data-key --key-id $KEY_ID --key-spec AES_256
{
    "CiphertextBlob": "S2Fybjphd3M6a21zOnVzLWVhc3QtMTowMDAwMDAwMDAwMDA6a2V5LzNmNWUzYmU5LTE5ZGYtNGVlNS04M2Q5LTY1NjRlYTdhMTUwYwAAAAA1Wn+vtJWo4MEEh9+iyHweOZzFmEo3BeQB+w2rYDPUiUi5O9E27h10jCoM1VgSeDVvI56A4AiZ4SSwIYc=",
    "Plaintext": "mD5LaDOpzDM/GcGggGS/NB7k9RpKXC1USiWYTCmiGig=",
    "KeyId": "arn:aws:kms:us-east-1:000000000000:key/3f5e3be9-19df-4ee5-83d9-6564ea7a150c"
}

# DataKey
$ echo "mD5LaDOpzDM/GcGggGS/NB7k9RpKXC1USiWYTCmiGig=" > plain_datakey

# Encrypted DataKey
## CiphertextBlobはbase64でエンコードされているので、デコードしてからファイルに入れる
$ echo "S2Fybjphd3M6a21zOnVzLWVhc3QtMTowMDAwMDAwMDAwMDA6a2V5LzNmNWUzYmU5LTE5ZGYtNGVlNS04M2Q5LTY1NjRlYTdhMTUwYwAAAAA1Wn+vtJWo4MEEh9+iyHweOZzFmEo3BeQB+w2rYDPUiUi5O9E27h10jCoM1VgSeDVvI56A4AiZ4SSwIYc=" | base64 --decode > encrypted_datakey
```

OpenSSLを利用し、DataKeyで平文メッセージを暗号化します。

```bash
$ openssl aes-256-cbc -e -in plain_message -out encrypted_message -pass file:plain_datakey

$ cat encrypted_message
Salted__??0o?50??i??????Rp?x?s?S@?3mG??~?{?r?="?,bFe
```

要済みの環境変数やファイルを削除します。

```bash
$ unset KEY_ID
$ rm plain_message plain_datakey

# 残ってるファイルは3つ
$ ls -l
total 24
-rw-r--r--  1 user  staff  678 Nov 21 12:06 docker-compose.yml
-rw-r--r--  1 user  staff  140 Nov 21 12:24 encrypted_datakey
-rw-r--r--  1 user  staff   64 Nov 21 12:26 encrypted_message
```

以上の操作により、2つの暗号化情報（encrypted_datakey、encrypted_message）が手元に残りました。

これらのテキスト情報はそのまま扱うと文字化けするため、以下のように base64 でエンコードした文字列を Git 管理することになります。

```bash
# 暗号化された認証情報をGit管理する時
# 文字化けを回避するため、base64エンコードしてからpushする
cat encrypted_datakey | base64 > encrypted_datakey_blob
cat encrypted_message | base64 > encrypted_message_blob
```

これらを利用し、次は復号化していきます。

## データを複号化する
KMSのDecryptを利用し、まずは暗号化されたDataKeyを複号化します。
DataKey自体がCMK(Master Key)情報を保持しているため、復号化処理ではCMKの指定が不要です。

```bash
# DataKeyを復号化
$ aws --endpoint-url http://localhost:4566 kms decrypt --ciphertext-blob fileb://encrypted_datakey
{
    "KeyId": "arn:aws:kms:us-east-1:000000000000:key/3f5e3be9-19df-4ee5-83d9-6564ea7a150c",
    "Plaintext": "mD5LaDOpzDM/GcGggGS/NB7k9RpKXC1USiWYTCmiGig=",
    "EncryptionAlgorithm": "SYMMETRIC_DEFAULT"
}

# Decrypted DataKey
$ echo "mD5LaDOpzDM/GcGggGS/NB7k9RpKXC1USiWYTCmiGig=" > decrypted_datakey
```

OpenSSLを利用し、復号化されたデータキーで暗号文を復号化する

```bash
$ openssl aes-256-cbc -d -in encrypted_message -pass file:decrypted_datakey
こんにちは
```

最初に用意したplain_messageと同様の文字列が取得できました。

基本的に、用が済んだDataKeyは即刻削除します。

```bash
$ rm decrypted_datakey

# 複号処理完了後も、残っているファイルは3つ
$ ls -l
total 24
-rw-r--r--  1 user  staff   45 Nov 21 12:38 decrypted_datakey
-rw-r--r--  1 user  staff   42 Nov 21 12:39 decrypted_message
-rw-r--r--  1 user  staff  678 Nov 21 12:06 docker-compose.yml
```

以上で、LocalStackのKMSとOpenSSLを利用したエンベロープ暗号化・復号化処理は終了です。

# おわりに
本記事では

- エンベロープ暗号化について
- AWS KMSとOpenSSLによるエンベロープ暗号化の方法

を説明しました。

本記事では説明の都合上、LocalStackから得られたKMSの実行結果をそのまま添付しています。AWSの実アカウントで運用する場合には、これらの値は外部公開せず、利用後済みやかに削除するようお願いします。

AWS KMSはMasterKey(CMK)の管理のみを担当しているため、作業途中に生じるDataKeyの管理は作業者に委ねられています。うっかり鍵を作業場に残してしまい、認証情報が流出してしまったとならないように、事前にLocalStackで練習していると安心です。