概要
個人情報のような機密性の高いデータを、アプリケーションがデータベースに保存する前に暗号化したいことがあります。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 にデータを送る必要もありません。


データベースに何を保存するか
| 保存するもの | 結果 |
|---|---|
| 暗号文だけ | 復号に使うデータキーが無いので、二度と復号できない |
| 暗号文 + データキーの ID | データキーに「ID」は無い。KMS キーの ID だけではデータキーは取り戻せない |
| 暗号文 + 平文のデータキー | 復号はできるが、データベースを読める人には誰でも復号できてしまう |
| 暗号文 + 暗号化したデータキー | 正しい。暗号化したデータキーは KMS の Decrypt を呼べる人しか開けない |
暗号化したデータキー(CiphertextBlob)には、どの KMS キーで暗号化したかの情報が含まれています。対称暗号化の KMS キーなら、Decrypt を呼ぶときに KMS キーを指定しなくても復号できます。



「鍵をなくすと困るから平文のまま横に置いておく」は、宝箱の上に鍵を置くのと同じでござる。置いてよいのは、長老の金庫でしか開かぬ封筒に入れた鍵だけでござるぞ。
AWS Encryption SDK
暗号文と暗号化したデータキーをまとめて扱うのは手間がかかるので、AWS Encryption SDK を使う方法もあります。Encryption SDK は、暗号化した結果を「暗号化メッセージ」という形式で返し、その中に暗号文と暗号化したデータキーを一緒に入れます。アプリケーションはこのメッセージを 1 つの値として保存すればよく、データキーの生成・暗号化・破棄の作法を SDK が引き受けます。
今回は流れを理解するために、GenerateDataKey と Decrypt を直接呼ぶ形で実装します。
実践
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 のキーを開きます。「暗号化設定」タブで、キーのタイプが「対称」、キーの用途が「暗号化および復号化」であることを確認します。


手順 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"暗号文と暗号化したデータキーのバイト数が表示されます。


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


手順 4: 保存したものだけで復号する
次のコマンドで、テーブルから読み出したものだけを使って復号します。
python3 envelope.py get customer-001customer-001: name=Banana Taro, card=4111-1111-1111-1111 と、元のレコードが表示されます(画像ではカード番号の部分を伏せています)。スクリプトは KMS キーの ID を Decrypt に渡していませんが、暗号化したデータキーにどの KMS キーで暗号化したかが含まれているので、復号できます。





宝箱と封筒(暗号化したデータキー)さえあれば、長老に封筒を開けてもらって宝箱を開けられたでござる。逆に、封筒をなくせば長老にも開けられぬ。だから必ず一緒にしまっておくのでござるな。
手順 5: KMS の使用状況を確かめる
KMS キーへのリクエストは CloudTrail に記録されます。CloudTrail のコンソールの「イベント履歴」で、イベントソース kms.amazonaws.com に絞ると、GenerateDataKey と Decrypt のイベントが記録されていることを確認できます(反映まで数分かかることがあります)。データの暗号化そのものは手元で行ったので、KMS に記録されるのはデータキーのやり取りだけです。
まとめ
| 保存するもの | 可否 |
|---|---|
| 暗号文 + 暗号化したデータキー | 正しい(エンベロープ暗号化の作法) |
| 暗号文 + 平文のデータキー | 不可(誰でも復号できる) |
| 暗号文だけ / 暗号文 + ID | 不可(復号できない) |
- エンベロープ暗号化は、データをデータキーで暗号化し、データキーを KMS キーで暗号化する
GenerateDataKeyは平文と暗号化済みの 2 つのデータキーを返す。平文は暗号化に使ったら捨て、暗号化済みのほうを暗号文と一緒に保存する- 復号では、保存した暗号化済みデータキーを
Decryptに渡して平文のデータキーを取り戻し、手元でデータを復号する - 対称暗号化の KMS キーなら、
Decryptに KMS キーを指定しなくてよい - AWS Encryption SDK を使うと、暗号文と暗号化したデータキーを 1 つのメッセージにまとめて扱える
参照先
- AWS KMS 暗号化の必須事項 – AWS Key Management Service
- データキーを生成する – AWS Key Management Service
- GenerateDataKey – AWS Key Management Service
- Decrypt – AWS Key Management Service
- AWS Encryption SDKとは – AWS Encryption SDK














