security
A
信頼度ランク
| S | 公式ソース確認済み |
| A | 成功実績多数・失敗例少数 |
| B | 賛否両論 |
| C | 動作未確認・セキュリティリスク高 |
| Z | 個人所感 |
KMSエンベロープ暗号化 — データキー・GenerateDataKey・パフォーマンスの仕組み
AWSサービスが内部で使うエンベロープ暗号化の仕組み、GenerateDataKeyとGenerateDataKeyWithoutPlaintext、データキーキャッシュによるKMSコスト削減、SDKでの実装方法を解説。
一言結論
大量データをKMSで暗号化する際はエンベロープ暗号化が基本で、データキーをローカルで使い暗号化済みキーだけを保存することでAPIコストとレイテンシを大幅に削減できる。
エンベロープ暗号化とは
KMSで大量のデータを暗号化する場合、データをKMSキー(CMK)で直接暗号化するのは非効率だ(KMSのAPIコール制限・コスト・レイテンシ)。その代わりに「データキー」という中間キーを使う仕組みがエンベロープ暗号化だ。
通常の暗号化:
データ → KMSキーで暗号化 → 暗号化データ
問題: 大量データをKMSに送るのはAPIコスト・レイテンシの問題あり
エンベロープ暗号化:
1. KMSに「データキー(DEK: Data Encryption Key)」を生成させる
2. データキー(平文)でデータをローカルで暗号化
3. データキー(暗号文)を暗号化データと一緒に保存
4. 復号時: 暗号化データキーをKMSで復号 → 平文データキーで復号
GenerateDataKeyの動作
import boto3
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
kms = boto3.client('kms')
# データキーを生成(平文と暗号文の両方を取得)
response = kms.generate_data_key(
KeyId='alias/my-key',
KeySpec='AES_256' # or AES_128
)
plaintext_key = response['Plaintext'] # 使ったら必ずメモリから削除
encrypted_key = response['CiphertextBlob'] # S3等に保存(安全)
# データをローカルで暗号化(KMSは使わない)
nonce = os.urandom(12)
aesgcm = AESGCM(plaintext_key)
ciphertext = aesgcm.encrypt(nonce, b"Secret data", None)
# 暗号化データキーとnonceを暗号文と一緒に保存
import pickle
encrypted_object = {
'encrypted_key': encrypted_key,
'nonce': nonce,
'ciphertext': ciphertext
}
# 平文データキーをメモリから削除(重要!)
del plaintext_key
# 復号
import boto3
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
kms = boto3.client('kms')
# 暗号化データキーをKMSで復号(KMS APIは1回のみ)
response = kms.decrypt(
CiphertextBlob=encrypted_object['encrypted_key']
)
plaintext_key = response['Plaintext']
# ローカルで復号
aesgcm = AESGCM(plaintext_key)
plaintext = aesgcm.decrypt(
encrypted_object['nonce'],
encrypted_object['ciphertext'],
None
)
del plaintext_key
GenerateDataKeyWithoutPlaintext
平文データキーが不要な場合(将来使うためにデータキーだけ生成しておく)に使う。
# 暗号化データキーのみ生成(平文は返さない)
response = kms.generate_data_key_without_plaintext(
KeyId='alias/my-key',
KeySpec='AES_256'
)
encrypted_key = response['CiphertextBlob']
# → 後でkms.decryptで平文データキーを取り出す
データキーキャッシュ
同じデータキーを短時間・複数回使い回すことでKMS APIコールを減らせる。
from aws_encryption_sdk import EncryptionSDKClient, CommitmentPolicy
from aws_encryption_sdk.caches.local import LocalCryptoMaterialsCache
from aws_encryption_sdk.materials_managers.caching import (
CachingCryptoMaterialsManager
)
import aws_encryption_sdk
enc_client = EncryptionSDKClient(
commitment_policy=CommitmentPolicy.REQUIRE_ENCRYPT_REQUIRE_DECRYPT
)
# ローカルキャッシュの設定
cache = LocalCryptoMaterialsCache(capacity=100)
# キャッシュマネージャー(同じデータキーを最大300秒・100回再利用)
caching_manager = CachingCryptoMaterialsManager(
master_key_provider=kms_key_provider,
cache=cache,
max_age=300.0, # 最大キャッシュ期間(秒)
max_messages_encrypted=100 # 最大使用回数
)
# 暗号化(キャッシュを活用)
ciphertext, _ = enc_client.encrypt(
source=data,
materials_manager=caching_manager
)
S3 Bucket KeyとのアナロジーWHAT
S3 Bucket Keyは本質的にエンベロープ暗号化のバケットレベル版だ。
通常のSSE-KMS:
各オブジェクト → KMSにGenerateDataKey → データキーで暗号化
→ オブジェクト数万個なら数万回のKMS API呼び出し
S3 Bucket Key有効時:
バケット用データキーをKMSが生成 → S3がキャッシュ
→ 各オブジェクトはバケットキーで暗号化
→ KMS API呼び出しを大幅削減(最大99%)
KMS API コールのコスト
東京リージョン(2026年時点参考):
$0.03 / 10,000 API コール
例: 100万オブジェクトのS3バケット(アップロード時)
通常のSSE-KMS: 100万 ÷ 10,000 × $0.03 = $3
S3 Bucket Key: ほぼ$0(KMS呼び出しが激減)
エンベロープ暗号化のメリット
1. パフォーマンス: KMSへのAPIコールは1回だけ、大量データはローカルで処理
2. コスト削減: KMSのAPIコール料金を最小化
3. サイズ制限回避: KMSの直接暗号化は4KBまで、エンベロープは無制限
4. 鍵の階層化: CMK(マスターキー)とDEK(データキー)を分離
試験頻出ポイント
| シナリオ | 回答 |
|---|---|
| KMSで1MBのファイルを直接暗号化できるか | 不可(4KB上限、エンベロープ暗号化が必要) |
| S3 Bucket Keyの目的 | KMS APIコール削減(最大99%)によるコスト削減 |
| GenerateDataKeyとDecryptの関係 | 暗号化時にGenerateDataKey、復号時にDecrypt |
| データキーをKMSが管理するか | しない(暗号化データキーはユーザーが管理) |
| エンベロープ暗号化でKMSにアクセスするタイミング | データキー生成時と復号時のみ |
まとめ
エンベロープ暗号化はKMSの中核的な設計パターンだ。CMKでデータを直接暗号化するのではなく、CMKでデータキーを保護し、実際のデータはデータキーでローカルに暗号化する。この設計によりパフォーマンス・コスト・サイズ制限の問題をすべて解決している。