KMS のエンベロープ暗号化でデータをデータベースに保存する方法 ── 暗号化したデータキーを一緒に持つ

目次

概要

個人情報のような機密性の高いデータを、アプリケーションがデータベースに保存する前に暗号化したいことがあります。AWS KMS を使うとき、KMS キーで直接暗号化できるのは 4 KB までで、大きなデータはデータキーで暗号化するエンベロープ暗号化を使います。

エンベロープ暗号化を自分のコードで実装するとき、迷いやすいのが「データベースに何を保存するか」です。暗号化したデータだけを保存すると、あとで復号できません。平文のデータキーを一緒に保存すると、暗号化した意味がなくなります。正解は、暗号化したデータと、暗号化したデータキーを一緒に保存することです。

この記事では、エンベロープ暗号化の流れを、アプリケーションのコードから見た手順として整理します。実践では、CloudShell で Python のコードを動かし、GenerateDataKey で得たデータキーでレコードを暗号化して DynamoDB に保存し、保存したものだけで復号できることを確かめます。

KMS の直接暗号化の 4 KB の制限と、CLI でファイルをエンベロープ暗号化する流れは、KMSの直接暗号化と4KBの壁 ── データキーによるエンベロープ暗号化 で扱っています。この記事は、アプリケーションがデータベースに保存する場面に絞ります。

暗号文と「暗号化したデータキー」を一緒に保存する
暗号文と「暗号化したデータキー」を一緒に保存する
クラウドおさる

宝箱(データ)に小さな鍵(データキー)をかけ、その鍵は長老の金庫(KMS)でしか開かぬ封筒に入れて、宝箱にくくり付けておく。封筒ごと持っていれば、あとで長老に開けてもらえるのでござる。

この記事のメリット

  • エンベロープ暗号化の流れを、GenerateDataKey と Decrypt の呼び出しとして説明できるようになる
  • データベースに保存すべきもの(暗号文と暗号化済みデータキー)と、保存してはいけないもの(平文のデータキー)を区別できるようになる
  • Python で、データキーを使った暗号化・保存・復号のコードを書けるようになる
  • AWS Encryption SDK が、この保存の作法をどう自動化しているかが分かる

執筆者とキャラクター紹介

執筆者:土肥(とひ)

株式会社ジェニュインの代表であり元エンジニア。これまでオンプレミスと AWS 環境のインフラ業務を担当。AWS 認定資格の学習で得た知識を、ハンズオンとともに解説するのが得意。直近では新人の教育も行っており、これから技術を学ぶ方が納得できる解説を心がけています。基礎編・Cloud Practitioner の記事では、解説役としても登場します。

土肥

クラウドおさる

クラウドおさる

はじめましてでござる。弊猿はクラウドおさる。IT インフラが異常に発達した猿山から、雲に乗って地上の AWS を学びに降りてきたのでござる。

クラウドおさる

基礎編や Cloud Practitioner の記事では、とひさんに素朴な疑問をぶつける役でござるな。Security - Specialty などの記事では、要所でワンポイントや注意点をひとこと添えるでござる。

クラウドおさる

猿山にもデータセンターや監視当番があって、地上で起きることはだいたい猿山にも同じ仕組みがあるのでござる。例え話がつい猿山に寄るのは、弊猿の癖でござる。

技術解説

エンベロープ暗号化の流れ

段階アプリケーションがすることKMS とのやり取り
1. データキーをもらうGenerateDataKey を呼ぶKMS キーを指定すると、平文のデータキーと、KMS キーで暗号化したデータキーの 2 つが返る
2. データを暗号化する平文のデータキーで、データを暗号化する(例: AES-GCM)なし(KMS の外で行う)
3. 保存する暗号文と暗号化したデータキーを一緒に保存し、平文のデータキーはメモリから消すなし
4. 復号する保存してある暗号化したデータキーを Decrypt に渡し、平文のデータキーを取り戻して、データを復号するDecrypt

KMS キー自体は KMS の外に出ません。KMS がやるのは、データキーの暗号化と復号だけです。大きなデータの暗号化はアプリケーションの手元で行うので、KMS にデータを送る必要もありません。

暗号化は手元で、KMS はデータキーの暗号化と復号だけを受け持つ
暗号化は手元で、KMS はデータキーの暗号化と復号だけを受け持つ

データベースに何を保存するか

保存するもの結果
暗号文だけ復号に使うデータキーが無いので、二度と復号できない
暗号文 + データキーの IDデータキーに「ID」は無い。KMS キーの ID だけではデータキーは取り戻せない
暗号文 + 平文のデータキー復号はできるが、データベースを読める人には誰でも復号できてしまう
暗号文 + 暗号化したデータキー正しい。暗号化したデータキーは KMS の Decrypt を呼べる人しか開けない

暗号化したデータキー(CiphertextBlob)には、どの KMS キーで暗号化したかの情報が含まれています。対称暗号化の KMS キーなら、Decrypt を呼ぶときに KMS キーを指定しなくても復号できます。

クラウドおさる

「鍵をなくすと困るから平文のまま横に置いておく」は、宝箱の上に鍵を置くのと同じでござる。置いてよいのは、長老の金庫でしか開かぬ封筒に入れた鍵だけでござるぞ。

AWS Encryption SDK

暗号文と暗号化したデータキーをまとめて扱うのは手間がかかるので、AWS Encryption SDK を使う方法もあります。Encryption SDK は、暗号化した結果を「暗号化メッセージ」という形式で返し、その中に暗号文と暗号化したデータキーを一緒に入れます。アプリケーションはこのメッセージを 1 つの値として保存すればよく、データキーの生成・暗号化・破棄の作法を SDK が引き受けます。

今回は流れを理解するために、GenerateDataKey と Decrypt を直接呼ぶ形で実装します。

新卒・未経験の方へ
クラウドおさる

AWS を仕事にしたい方へ ── 新卒・未経験から

この記事を書いているのは、AWS の設計・構築を仕事にしている現役エンジニアです。運営元の株式会社ジェニュインでは、新卒・未経験の方も募集しています。異業種や文系からインフラエンジニアになり活躍しているメンバーもいます。

  • ✓システム開発・WEB エンジニア / インフラ構築などの職種で、未経験から応募できます
  • ✓未経験から入社した社員の声を含む社員アンケートを掲載
  • ✓応募フォームから 1 分で応募できます

実践

ハンズオンガイド7 ステップ画面 4 枚

コンソールの横に開いて、チェックしながら進めましょう。

ガイドを開く

別タブで開きます。チェックはこのブラウザに保存されます。

CloudShell で Python のスクリプトを動かし、レコードを暗号化して DynamoDB の dva-secure-records に保存します。保存した項目に平文が残っていないこと、保存した項目と KMS の Decrypt だけで元に戻せることを確かめます。

前提条件

  • リージョン: ap-northeast-1(東京)
  • 以下のリソースが作成済みであること

まだ用意していない場合は、「作り方の参考」の記事の手順で作れます。リソース名は、この記事の名前に読み替えてください。

リソース種別リソース名内容作り方の参考
KMS キーalias/dva-envelope-labカスタマー管理キー(対称・暗号化と復号)AWS KMS で暗号鍵を作成しデータを暗号化・復号する
DynamoDB テーブルdva-secure-recordsパーティションキー record_id(文字列)Amazon DynamoDB でテーブルを作り Lambda から読み書きする

作成するリソース一覧

リソース種別リソース名用途
DynamoDB の項目customer-001暗号化して保存するレコード

手順 1: KMS キーを確かめる

AWS マネジメントコンソールにログインし、リージョンが ap-northeast-1(東京)であることを確認します。上部の検索バーに KMS と入力し、「Key Management Service」を選択します。左側ナビゲーションの「カスタマー管理型のキー」で、エイリアスが dva-envelope-lab のキーを開きます。「暗号化設定」タブで、キーのタイプが「対称」、キーの用途が「暗号化および復号化」であることを確認します。

KMS キー dva-envelope-lab の暗号化設定(対称・暗号化および復号化)
KMS キー dva-envelope-lab の暗号化設定(対称・暗号化および復号化)

手順 2: スクリプトを用意する

コンソール上部の CloudShell のアイコンから CloudShell を開き、使うライブラリを入れます。

pip3 install --user boto3 cryptography

次の内容で envelope.py を作ります(cat > envelope.py <<'EOF' で貼り付け、最後に EOF と入力します)。

import os
import sys

import boto3
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

kms = boto3.client("kms", region_name="ap-northeast-1")
table = boto3.resource("dynamodb", region_name="ap-northeast-1").Table("dva-secure-records")
KEY_ID = "alias/dva-envelope-lab"


def put(record_id, plaintext):
    # 1. データキーをもらう(平文と、KMS キーで暗号化したものの 2 つが返る)
    dk = kms.generate_data_key(KeyId=KEY_ID, KeySpec="AES_256")
    # 2. 平文のデータキーで、手元でデータを暗号化する
    nonce = os.urandom(12)
    ciphertext = AESGCM(dk["Plaintext"]).encrypt(nonce, plaintext.encode("utf-8"), None)
    # 3. 暗号文と「暗号化したデータキー」を一緒に保存する。平文のデータキーは保存しない
    table.put_item(Item={
        "record_id": record_id,
        "ciphertext": ciphertext,
        "nonce": nonce,
        "encrypted_data_key": dk["CiphertextBlob"],
    })
    print(f"saved {record_id}: ciphertext {len(ciphertext)} bytes, encrypted data key {len(dk['CiphertextBlob'])} bytes")


def get(record_id):
    item = table.get_item(Key={"record_id": record_id})["Item"]
    # 4. 暗号化したデータキーを KMS で復号し、手元でデータを復号する
    data_key = kms.decrypt(CiphertextBlob=item["encrypted_data_key"].value)["Plaintext"]
    plaintext = AESGCM(data_key).decrypt(item["nonce"].value, item["ciphertext"].value, None)
    print(f"{record_id}: {plaintext.decode('utf-8')}")


if __name__ == "__main__":
    if sys.argv[1] == "put":
        put(sys.argv[2], sys.argv[3])
    else:
        get(sys.argv[2])

手順 3: 暗号化して保存する

次のコマンドで、レコードを暗号化して保存します。

python3 envelope.py put customer-001 "name=Banana Taro, card=4111-1111-1111-1111"

暗号文と暗号化したデータキーのバイト数が表示されます。

CloudShell の実行結果(暗号化して保存したレコードのバイト数)
CloudShell の実行結果(暗号化して保存したレコードのバイト数)

DynamoDB のコンソールの左側ナビゲーションで「項目を探索」を選択し、テーブルの一覧から dva-secure-records を選んで、customer-001 のリンクを開きます。ciphertext・nonce・encrypted_data_key はバイナリ(Base64 で表示)で、どこにも元の文字列やデータキーの平文は入っていません。

DynamoDB の項目 customer-001(暗号文と暗号化したデータキーがバイナリで保存されている状態)
DynamoDB の項目 customer-001(暗号文と暗号化したデータキーがバイナリで保存されている状態)

手順 4: 保存したものだけで復号する

次のコマンドで、テーブルから読み出したものだけを使って復号します。

python3 envelope.py get customer-001

customer-001: name=Banana Taro, card=4111-1111-1111-1111 と、元のレコードが表示されます(画像ではカード番号の部分を伏せています)。スクリプトは KMS キーの ID を Decrypt に渡していませんが、暗号化したデータキーにどの KMS キーで暗号化したかが含まれているので、復号できます。

CloudShell の実行結果(保存した項目から復号できた状態)
CloudShell の実行結果(保存した項目から復号できた状態)
クラウドおさる

宝箱と封筒(暗号化したデータキー)さえあれば、長老に封筒を開けてもらって宝箱を開けられたでござる。逆に、封筒をなくせば長老にも開けられぬ。だから必ず一緒にしまっておくのでござるな。

手順 5: KMS の使用状況を確かめる

KMS キーへのリクエストは CloudTrail に記録されます。CloudTrail のコンソールの「イベント履歴」で、イベントソース kms.amazonaws.com に絞ると、GenerateDataKey と Decrypt のイベントが記録されていることを確認できます(反映まで数分かかることがあります)。データの暗号化そのものは手元で行ったので、KMS に記録されるのはデータキーのやり取りだけです。

まとめ

保存するもの可否
暗号文 + 暗号化したデータキー正しい(エンベロープ暗号化の作法)
暗号文 + 平文のデータキー不可(誰でも復号できる)
暗号文だけ / 暗号文 + ID不可(復号できない)
  • エンベロープ暗号化は、データをデータキーで暗号化し、データキーを KMS キーで暗号化する
  • GenerateDataKey は平文と暗号化済みの 2 つのデータキーを返す。平文は暗号化に使ったら捨て、暗号化済みのほうを暗号文と一緒に保存する
  • 復号では、保存した暗号化済みデータキーを Decrypt に渡して平文のデータキーを取り戻し、手元でデータを復号する
  • 対称暗号化の KMS キーなら、Decrypt に KMS キーを指定しなくてよい
  • AWS Encryption SDK を使うと、暗号文と暗号化したデータキーを 1 つのメッセージにまとめて扱える

参照先

キャリア相談
クラウドおさる

独学の先に、AWS の仕事がある ── ジュニアおさるのキャリア相談

CLF には受かったけれど、次に何をすればいいのか分からない。独学で AWS を学ぶジュニアおさるの相談に、株式会社ジェニュイン代表の土肥が答えています。

  • ✓資格はゴールか、CLF の次に何から手を付けるか
  • ✓EC2 の次に作ってみる構成と、IaC・コスト管理
  • ✓実務経験ゼロでも、面接で話せる「これまで」の作り方
新卒・未経験の方へ
クラウドおさる

AWS を仕事にしたい方へ ── 新卒・未経験から

この記事を書いているのは、AWS の設計・構築を仕事にしている現役エンジニアです。運営元の株式会社ジェニュインでは、新卒・未経験の方も募集しています。異業種や文系からインフラエンジニアになり活躍しているメンバーもいます。

  • ✓システム開発・WEB エンジニア / インフラ構築などの職種で、未経験から応募できます
  • ✓未経験から入社した社員の声を含む社員アンケートを掲載
  • ✓応募フォームから 1 分で応募できます

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

株式会社ジェニュイン(https://genuine-pt.jp)の代表取締役.

2018年〜インフラエンジニアとしてキャリアをスタートし、オンプレミスのネットワーク・サーバ環境で3年半、クラウド環境で4年半の8年間エンジニアとして従事。
2021年に佐藤氏の創業した株式会社Luxyを引き継ぎ、代表に就任。
その後アガルートグループ内の株式会社ジェニュインと合併。
合併後同社代表に就任。

目次