- AWS
AWS KMSの使い方|CLIとboto3で暗号化・復号・データキー
AWS KMS(Key Management Service)は、暗号化に使うキーを AWS の中で保管し、API 経由で暗号化・復号をしてくれるサービスです。 キーそのものは KMS の外に出てこないので、利用者は「このキーで暗号化して」「この暗号文を戻して」と頼む形で使います。
使い方は2通りあります。4096バイトまでの小さなデータは encrypt / decrypt で直接暗号化し、それより大きいデータは generate-data-key で受け取ったデータキーを使って手元で暗号化します(エンベロープ暗号化)。この記事では、この両方を AWS CLI と boto3 で最初から最後まで通し、各段の出力と、つまずきやすい箇所のエラー文を載せます。
確認環境:Ubuntu 24.04、AWS CLI 1.46.1、Python 3.11.15、boto3 1.43.103、cryptography 46.0.7、OpenSSL 3.0.13。AWS の API をローカルで再現するツール(moto 5.2.3)で実行しています。掲載するコマンドは、実際の AWS でそのまま使える形(--endpoint-url なし)で書いています。
KMSのAPI早見表(用途と必要な権限)
| やりたいこと | CLI(aws kms 〜) | boto3 | 必要な権限 |
|---|---|---|---|
| キーを作る | create-key | create_key | kms:CreateKey(IAMポリシー) |
| キーに名前を付ける | create-alias | create_alias | kms:CreateAlias(エイリアス側はIAMポリシー、キー側はキーポリシー) |
| キーの情報を見る | describe-key | describe_key | kms:DescribeKey(キーポリシー) |
| エイリアスを一覧する | list-aliases | list_aliases | kms:ListAliases(IAMポリシー) |
| 4096バイトまでを暗号化 | encrypt | encrypt | kms:Encrypt(キーポリシー) |
| 復号する | decrypt | decrypt | kms:Decrypt(キーポリシー) |
| データキーを作る(平文つき) | generate-data-key | generate_data_key | kms:GenerateDataKey(キーポリシー) |
| データキーを作る(暗号化済みのみ) | generate-data-key-without-plaintext | generate_data_key_without_plaintext | kms:GenerateDataKeyWithoutPlaintext(キーポリシー) |
| 別のキーで暗号化し直す | re-encrypt | re_encrypt | kms:ReEncryptFrom(元のキー)、kms:ReEncryptTo(先のキー) |
| キーポリシーを見る・変える | get-key-policy / put-key-policy | get_key_policy / put_key_policy | kms:GetKeyPolicy / kms:PutKeyPolicy(キーポリシー) |
| 自動ローテーションを有効にする | enable-key-rotation | enable_key_rotation | kms:EnableKeyRotation(キーポリシー) |
| キーの削除を予約する | schedule-key-deletion | schedule_key_deletion | kms:ScheduleKeyDeletion(キーポリシー) |
権限の列は、AWS KMS の権限リファレンスと各 API の説明にある「Required permissions」に沿っています。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「AWS KMSの使い方|CLIとboto3で暗号化・復号・データキー」
2つの使い方の違い
| 項目 | 直接暗号化(encrypt / decrypt) | エンベロープ暗号化(generate-data-key) |
|---|---|---|
| 暗号化できる大きさ | 4096バイトまで | 制限なし(手元で暗号化するため) |
| 暗号化の計算をする場所 | KMS の中 | 手元(アプリケーション) |
| KMS に送るもの | データそのもの | データキーだけ |
| 向いているもの | パスワード、トークン、設定値 | ファイル、大きなデータ |
| 保存するもの | 暗号文 | 暗号文 + 暗号化されたデータキー |
flowchart TB
subgraph 暗号化
A[generate-data-key] --> B[平文のデータキー]
A --> C[暗号化されたデータキー]
B --> D[手元でデータをAES暗号化]
D --> E[暗号文と暗号化されたデータキーを一緒に保存]
C --> E
end
subgraph 復号
F[暗号化されたデータキーを decrypt] --> G[平文のデータキー]
G --> H[手元でデータを復号]
end
AWS CLIで暗号化と復号を通す
キーを作る(create-key)
KEY_ID=$(aws kms create-key --description "demo app key" \
--query KeyMetadata.KeyId --output text)
echo $KEY_ID
aws kms describe-key --key-id $KEY_ID
435a4396-f31f-4d4a-ab54-5764e2d7b098
{
"KeyMetadata": {
"AWSAccountId": "123456789012",
"KeyId": "435a4396-f31f-4d4a-ab54-5764e2d7b098",
"Arn": "arn:aws:kms:ap-northeast-1:123456789012:key/435a4396-f31f-4d4a-ab54-5764e2d7b098",
"CreationDate": 1791180357.742485,
"Enabled": true,
"Description": "demo app key",
"KeyState": "Enabled",
"Origin": "AWS_KMS",
"KeyManager": "CUSTOMER",
"CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
"KeySpec": "SYMMETRIC_DEFAULT",
"EncryptionAlgorithms": [
"SYMMETRIC_DEFAULT"
],
"SigningAlgorithms": []
}
}
オプションを付けずに作ると、KeySpec が SYMMETRIC_DEFAULT の対称キー(暗号化と復号に同じキーを使う種類)になります。この記事で扱うのはこの対称キーです。
エイリアスを付ける(create-alias)
キーIDは長いので、alias/ で始まる名前(エイリアス)を付けておくと、以降は --key-id にその名前を書けます。
aws kms create-alias --alias-name alias/demo-app --target-key-id $KEY_ID
aws kms list-aliases --query 'Aliases[?AliasName==`alias/demo-app`]'
[
{
"AliasName": "alias/demo-app",
"AliasArn": "arn:aws:kms:ap-northeast-1:123456789012:alias/demo-app",
"TargetKeyId": "435a4396-f31f-4d4a-ab54-5764e2d7b098"
}
]
--key-id に書ける形は次の4つです。
| 形 | 例 |
|---|---|
| キーID | 435a4396-f31f-4d4a-ab54-5764e2d7b098 |
| キーARN | arn:aws:kms:ap-northeast-1:123456789012:key/435a4396-… |
| エイリアス名 | alias/demo-app |
| エイリアスARN | arn:aws:kms:ap-northeast-1:123456789012:alias/demo-app |
暗号化する(encrypt)
printf 'db-password=S3cret!' > secret.txt
aws kms encrypt --key-id alias/demo-app --plaintext fileb://secret.txt
{
"CiphertextBlob": "(Base64 の文字列。長いので省略)",
"KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/435a4396-f31f-4d4a-ab54-5764e2d7b098"
}
CLI の出力に入っている CiphertextBlob は、暗号文(バイナリ)を Base64 で文字にしたものです。 ファイルに保存するときは、Base64 を戻してバイナリにします。
aws kms encrypt --key-id alias/demo-app --plaintext fileb://secret.txt \
--query CiphertextBlob --output text | base64 -d > secret.enc
復号する(decrypt)
aws kms decrypt --ciphertext-blob fileb://secret.enc
{
"KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/435a4396-f31f-4d4a-ab54-5764e2d7b098",
"Plaintext": "ZGItcGFzc3dvcmQ9UzNjcmV0IQ=="
}
Plaintext も Base64 で返ります。元の文字に戻すには、ここでも base64 -d を通します。
aws kms decrypt --ciphertext-blob fileb://secret.enc \
--query Plaintext --output text | base64 -d
db-password=S3cret!
decrypt に --key-id を付けていない点に注目してください。API リファレンスによると、対称キーの場合、KMS は暗号文に含めたメタデータから使うキーを判断できます。ただし、意図したキーを確実に使うために、KeyId の指定が推奨されています。
fileb:// と Base64 の扱い
KMS の CLI でいちばん迷いやすいのが、バイナリの受け渡しです。整理すると次のとおりです。
| 場面 | 形式 | 書き方 |
|---|---|---|
| --plaintext にファイルを渡す | そのままのバイナリ | fileb://secret.txt |
| encrypt の出力(CiphertextBlob) | Base64 の文字列 | base64 -d でバイナリにして保存 |
| --ciphertext-blob にファイルを渡す | そのままのバイナリ | fileb://secret.enc |
| decrypt の出力(Plaintext) | Base64 の文字列 | base64 -d で元のデータに戻す |
入力は fileb:// でバイナリのまま渡し、出力は base64 -d で戻す、と覚えると迷いません。
fileb:// と file:// の違い
AWS CLI ユーザーガイドの「Blob」の説明では、次のように書かれています。
| プレフィックス | 扱い |
|---|---|
| fileb:// | 常に、エンコードされていない生のバイナリとして扱う |
| file:// | 設定 cli_binary_format の値で変わる。値が base64(AWS CLI v2 の既定)のときは、ファイルの中身を Base64 のテキストとして扱う。値が raw-in-base64-out(AWS CLI v1 の既定)のときは、テキストとして読んでバイナリに変換する |
fileb:// はどちらの版でも同じ動きなので、KMS では fileb:// を使うのが確実です。
AWS CLI v2 で文字列を直接渡すとき
AWS CLI v2 の移行ガイドによると、v2 は既定で、バイナリの入力・出力パラメータをすべて Base64 の文字列として受け渡しします。v1 の動きに戻すには、設定ファイルの cli_binary_format か、--cli-binary-format オプションを使います。
そのため、v2 で --plaintext に文字列をそのまま書くときは、ユーザーガイドにある設定値 raw-in-base64-out を --cli-binary-format に指定するか、ファイル経由にします。
# 1. 文字列をそのまま渡したいとき(v1 と同じ扱いにする)
aws kms encrypt --key-id alias/demo-app --plaintext 'hello' \
--cli-binary-format raw-in-base64-out
# 2. ファイル経由にする(v1 でも v2 でも同じ)
aws kms encrypt --key-id alias/demo-app --plaintext fileb://secret.txt
今回の確認環境(AWS CLI 1.46.1)では、--plaintext 'hello' をそのまま渡して暗号化し、復号して hello に戻ることを確かめています。
aws kms encrypt --key-id alias/demo-app --plaintext 'hello' \
--query CiphertextBlob --output text | base64 -d > h.enc
aws kms decrypt --ciphertext-blob fileb://h.enc --query Plaintext --output text | base64 -d
hello
エラーになる例と直し方
4096バイトを超えると ValidationException
encrypt に渡せる平文は4096バイトまでです。4096バイトちょうどと、4097バイトで試した結果です。
head -c 4096 /dev/zero > ok.bin
head -c 4097 /dev/zero > big.bin
aws kms encrypt --key-id alias/demo-app --plaintext fileb://ok.bin --query 'KeyId'
aws kms encrypt --key-id alias/demo-app --plaintext fileb://big.bin
"arn:aws:kms:ap-northeast-1:123456789012:key/435a4396-f31f-4d4a-ab54-5764e2d7b098"
An error occurred (ValidationException) when calling the Encrypt operation: 1 validation error detected: Value at 'plaintext' failed to satisfy constraint: Member must have length less than or equal to 4096
4096バイトを超えるデータは、後述のデータキーを使う方法(エンベロープ暗号化)に切り替えます。
壊れたBlobは InvalidCiphertextException
暗号文が途中で切れている、1バイト書き換わっている、そもそも暗号文ではない、の3通りを試しました。
head -c 40 secret.enc > broken.enc # 途中で切れた暗号文
python3 -c "
b = bytearray(open('secret.enc','rb').read()); b[-1] ^= 1
open('flip.enc','wb').write(b)" # 最後の1バイトを書き換えた暗号文
printf 'not a ciphertext' > junk.enc # 暗号文ではないデータ
aws kms decrypt --ciphertext-blob fileb://broken.enc
aws kms decrypt --ciphertext-blob fileb://flip.enc
aws kms decrypt --ciphertext-blob fileb://junk.enc
An error occurred (InvalidCiphertextException) when calling the Decrypt operation:
An error occurred (InvalidCiphertextException) when calling the Decrypt operation:
An error occurred (InvalidCiphertextException) when calling the Decrypt operation:
どれも同じエラーで、メッセージの本文は空です。API リファレンスでは、InvalidCiphertextException は「指定された暗号文、または暗号化コンテキストのように暗号文に組み込まれた追加の認証データが、壊れている・欠けている・その他の理由で無効」な場合のエラーと説明されています。
このエラーが出たら、次の順で確かめます。
| 確認すること | 見るところ |
|---|---|
| 暗号文をバイナリで保存したか | encrypt の出力を base64 -d に通したか |
| バイナリのまま渡しているか | --ciphertext-blob に fileb:// を使っているか |
| 保存や転送の途中で中身が変わっていないか | ファイルサイズ、改行の追加、文字コード変換 |
| 暗号化コンテキストが同じか | 次の節 |
EncryptionContext が合わないと復号できない
EncryptionContext(暗号化コンテキスト)は、暗号化のときに添えるキーと値の組です。秘密の情報ではなく、「この暗号文は何のためのものか」を示すラベルとして使います。暗号化のときに付けたら、復号のときにも同じものを渡す必要があります。
aws kms encrypt --key-id alias/demo-app --plaintext fileb://secret.txt \
--encryption-context app=orders,env=dev \
--query CiphertextBlob --output text | base64 -d > ctx.enc
# 同じコンテキスト:復号できる
aws kms decrypt --ciphertext-blob fileb://ctx.enc \
--encryption-context app=orders,env=dev --query Plaintext --output text
# 値が違う
aws kms decrypt --ciphertext-blob fileb://ctx.enc \
--encryption-context app=orders,env=prod
# コンテキストを付けない
aws kms decrypt --ciphertext-blob fileb://ctx.enc
ZGItcGFzc3dvcmQ9UzNjcmV0IQ==
An error occurred (InvalidCiphertextException) when calling the Decrypt operation:
An error occurred (InvalidCiphertextException) when calling the Decrypt operation:
API リファレンスには、暗号化コンテキストは大文字小文字まで完全に一致させる必要があると書かれています。エラー名からは気づきにくいので、InvalidCiphertextException が出たら、暗号文の破損とあわせてコンテキストの違いも確かめます。
別のキーを指定したとき・キーが使えない状態のとき
Decrypt の API リファレンスには、次のエラーが定義されています。
| エラー | API リファレンスの説明 |
|---|---|
| IncorrectKeyException | 指定した KMS キーではデータを復号できない。Decrypt の KeyId は、暗号化に使ったのと同じ KMS キーを指している必要がある |
| DisabledException | 指定した KMS キーが有効になっていない |
| KMSInvalidStateException | 指定したリソースの状態が、このリクエストに対して有効ではない |
| NotFoundException | 指定したエンティティまたはリソースが見つからない |
存在しないエイリアスを指定したときの NotFoundException は、次のように出ます。
aws kms describe-key --key-id alias/nope
An error occurred (NotFoundException) when calling the DescribeKey operation: Alias arn:aws:kms:ap-northeast-1:123456789012:alias/nope is not found.
メッセージにリージョンとアカウントIDが入っているので、意図したリージョン・アカウントを見ているかをここで確かめられます。
データキーで大きなデータを暗号化する(エンベロープ暗号化)
データキーを作る(generate-data-key)
aws kms generate-data-key --key-id alias/demo-app --key-spec AES_256
{
"CiphertextBlob": "(暗号化されたデータキー。Base64。省略)",
"Plaintext": "WJ+2XPyF29oUVmviqRII3UuQZ9ptOb8Q4OH9pLc2rNQ=",
"KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/435a4396-f31f-4d4a-ab54-5764e2d7b098"
}
| 項目 | 中身 | 扱い |
|---|---|---|
| Plaintext | 平文のデータキー(AES_256 なら32バイト) | 暗号化に使ったらすぐ消す。保存しない |
| CiphertextBlob | 同じデータキーを KMS キーで暗号化したもの | 暗号化したデータと一緒に保存する |
--key-spec は AES_256 か AES_128 です。API リファレンスでは、使い方が次の手順で示されています。
- GenerateDataKey でデータキーを取得する
- 平文のデータキーで、KMS の外でデータを暗号化する。終わったら平文のデータキーをメモリから消す
- 暗号化されたデータキーを、暗号化したデータと一緒に保存する
- 復号するときは、Decrypt で暗号化されたデータキーを平文に戻し、そのキーでデータを復号して、平文のデータキーを消す
平文のキーが要らない場面(あとで別の処理が復号して使う場合など)では、generate-data-key-without-plaintext を使うと CiphertextBlob だけが返ります。
aws kms generate-data-key-without-plaintext --key-id alias/demo-app --key-spec AES_256
{
"CiphertextBlob": "(暗号化されたデータキー。Base64。省略)",
"KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/435a4396-f31f-4d4a-ab54-5764e2d7b098"
}
CLI と openssl でファイルを暗号化・復号する
printf 'id,name,qty\n1,desk,12\n2,chair,30\n' > report.csv
# 1. データキーを作る(平文のキーと、KMSキーで暗号化されたキーが返る)
aws kms generate-data-key --key-id alias/demo-app --key-spec AES_256 > datakey.json
python3 -c "
import json,base64
d=json.load(open('datakey.json'))
open('datakey.bin','wb').write(base64.b64decode(d['Plaintext']))
open('datakey.enc','wb').write(base64.b64decode(d['CiphertextBlob']))"
rm datakey.json
# 2. 平文のデータキーでファイルを暗号化し、平文のキーはすぐ消す
openssl enc -aes-256-cbc -pbkdf2 -in report.csv -out report.csv.enc -pass file:datakey.bin
rm datakey.bin
ls report.csv.enc datakey.enc
# 3. 復号:暗号化されたデータキーを KMS で戻し、そのキーでファイルを復号
aws kms decrypt --ciphertext-blob fileb://datakey.enc \
--query Plaintext --output text | base64 -d > datakey.bin
openssl enc -d -aes-256-cbc -pbkdf2 -in report.csv.enc -out report.dec.csv -pass file:datakey.bin
rm datakey.bin
cat report.dec.csv
cmp report.csv report.dec.csv && echo "元のファイルと一致"
datakey.enc
report.csv.enc
id,name,qty
1,desk,12
2,chair,30
元のファイルと一致
手元に残るのは report.csv.enc(暗号化したデータ)と datakey.enc(暗号化されたデータキー)の2つです。datakey.enc は KMS キーの kms:Decrypt 権限がないと平文に戻せないので、2つを一緒に保存しておけます。
boto3で暗号化と復号を通す
boto3 では、Plaintext や CiphertextBlob を bytes で直接やり取りします。 Base64 への変換は boto3 が内部で行うので、CLI のような base64 -d は要りません。
import boto3
from botocore.exceptions import ClientError
kms = boto3.client("kms", region_name="ap-northeast-1")
# 1. キーとエイリアスを作る
key = kms.create_key(Description="demo app key (boto3)")["KeyMetadata"]
print("1 KeyId :", key["KeyId"], key["KeySpec"], key["KeyState"])
kms.create_alias(AliasName="alias/demo-app-py", TargetKeyId=key["KeyId"])
# 2. 暗号化(Plaintext は bytes で渡す。戻り値の CiphertextBlob も bytes)
enc = kms.encrypt(KeyId="alias/demo-app-py", Plaintext=b"db-password=S3cret!")
blob = enc["CiphertextBlob"]
print("2 encrypt :", type(blob).__name__, "/ KeyId =", enc["KeyId"])
# 3. 復号(対称キーなら KeyId は省略できる)
dec = kms.decrypt(CiphertextBlob=blob)
print("3 decrypt :", dec["Plaintext"])
# 4. EncryptionContext を付けた暗号化と復号
ctx = {"app": "orders", "env": "dev"}
blob2 = kms.encrypt(KeyId="alias/demo-app-py", Plaintext=b"token-123", EncryptionContext=ctx)["CiphertextBlob"]
print("4 context :", kms.decrypt(CiphertextBlob=blob2, EncryptionContext=ctx)["Plaintext"])
def try_call(label, **kwargs):
try:
fn = kwargs.pop("fn")
fn(**kwargs)
print(label, "成功")
except ClientError as e:
print(label, e.response["Error"]["Code"])
# 5. エラーになる例
try_call("5a コンテキスト違い :", fn=kms.decrypt, CiphertextBlob=blob2, EncryptionContext={"app": "orders", "env": "prod"})
try_call("5b コンテキストなし :", fn=kms.decrypt, CiphertextBlob=blob2)
try_call("5c 途中で切れたBlob :", fn=kms.decrypt, CiphertextBlob=blob[:40])
try_call("5d 4097バイトの平文 :", fn=kms.encrypt, KeyId="alias/demo-app-py", Plaintext=b"x" * 4097)
try_call("5e 存在しないエイリアス:", fn=kms.encrypt, KeyId="alias/no-such-key", Plaintext=b"x")
1 KeyId : 6a92de6b-30aa-443e-9491-610615d6e9f6 SYMMETRIC_DEFAULT Enabled
2 encrypt : bytes / KeyId = arn:aws:kms:ap-northeast-1:123456789012:key/6a92de6b-30aa-443e-9491-610615d6e9f6
3 decrypt : b'db-password=S3cret!'
4 context : b'token-123'
5a コンテキスト違い : InvalidCiphertextException
5b コンテキストなし : InvalidCiphertextException
5c 途中で切れたBlob : InvalidCiphertextException
5d 4097バイトの平文 : ValidationException
5e 存在しないエイリアス: NotFoundException
| 項目 | CLI | boto3 |
|---|---|---|
| 平文の渡し方 | --plaintext fileb://ファイル | Plaintext=b"..."(bytes) |
| 暗号文の受け取り | Base64 の文字列(base64 -d で戻す) | bytes |
| 暗号文の渡し方 | --ciphertext-blob fileb://ファイル | CiphertextBlob=bytes |
| 暗号化コンテキスト | --encryption-context app=orders,env=dev | EncryptionContext={"app": "orders", "env": "dev"} |
| エラーの判定 | 終了コード255とメッセージ | e.response["Error"]["Code"] または kms.exceptions.〜 |
暗号文を DB やテキストファイルに保存したいときは、自分で base64.b64encode(blob).decode() として文字列にし、復号の前に base64.b64decode で bytes に戻します。
boto3 でエンベロープ暗号化(AES-256-GCM)
データキーでの暗号化には、cryptography パッケージの AESGCM を使います(pip install cryptography)。暗号化されたデータキーを暗号文の先頭に入れて、1つのファイルにまとめる形です(envelope.py)。
import os
import struct
import sys
import boto3
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
kms = boto3.client("kms", region_name="ap-northeast-1")
KEY_ID = "alias/demo-app"
def encrypt_file(src, dst, context):
# 1. データキーを作る(Plaintext=平文のキー、CiphertextBlob=KMSキーで暗号化されたキー)
dk = kms.generate_data_key(KeyId=KEY_ID, KeySpec="AES_256", EncryptionContext=context)
# 2. 平文のキーで、手元でデータを暗号化する(AES-256-GCM)
nonce = os.urandom(12)
data = open(src, "rb").read()
body = AESGCM(dk["Plaintext"]).encrypt(nonce, data, None)
# 3. 「暗号化されたデータキー」と暗号文を1つのファイルにまとめる
enc_key = dk["CiphertextBlob"]
with open(dst, "wb") as f:
f.write(struct.pack(">H", len(enc_key))) # 先頭2バイト:暗号化キーの長さ
f.write(enc_key)
f.write(nonce)
f.write(body)
del dk # 平文のキーは保存しない
return len(data)
def decrypt_file(src, context):
raw = open(src, "rb").read()
(n,) = struct.unpack(">H", raw[:2])
enc_key, nonce, body = raw[2 : 2 + n], raw[2 + n : 14 + n], raw[14 + n :]
# 4. 暗号化されたデータキーを KMS で平文に戻す(KMS を呼ぶのはここだけ)
key = kms.decrypt(CiphertextBlob=enc_key, EncryptionContext=context)["Plaintext"]
# 5. 戻したキーで、手元でデータを復号する
return AESGCM(key).decrypt(nonce, body, None)
if __name__ == "__main__":
ctx = {"file": "big.csv"}
with open("big.csv", "w") as f:
f.write("id,name,qty\n")
for i in range(200000):
f.write(f"{i},item{i},{i % 50}\n")
size = encrypt_file("big.csv", "big.csv.enc", ctx)
print(f"元のサイズ : {size:,} バイト(4096バイトの上限を超えるデータ)")
plain = decrypt_file("big.csv.enc", ctx)
print("復号して一致 :", plain == open("big.csv", "rb").read())
print("先頭の2行 :", plain.decode().splitlines()[:2])
try:
decrypt_file("big.csv.enc", {"file": "other.csv"})
except kms.exceptions.InvalidCiphertextException as e:
print("コンテキスト違い:", type(e).__name__)
元のサイズ : 3,937,792 バイト(4096バイトの上限を超えるデータ)
復号して一致 : True
先頭の2行 : ['id,name,qty', '0,item0,0']
コンテキスト違い: InvalidCiphertextException
約3.9MBのファイルでも、KMS に送っているのはデータキーの生成と復号の2回だけです。ファイルの保存形式は次のとおりです。
| 位置 | 中身 |
|---|---|
| 先頭2バイト | 暗号化されたデータキーの長さ |
| 続く n バイト | 暗号化されたデータキー(CiphertextBlob) |
| 続く12バイト | ノンス(暗号化のたびに新しく作る乱数) |
| 残り | AES-256-GCM の暗号文(末尾に認証タグを含む) |
キーポリシーの読み方
キーポリシーは、KMS キーごとに付く「誰がこのキーで何をできるか」を決める JSON です。 まず、作成直後のポリシーを見てみます。
aws kms get-key-policy --key-id $KEY_ID --policy-name default --output text
{"Version": "2012-10-17", "Id": "key-default-1", "Statement": [{"Sid": "Enable IAM User Permissions", "Effect": "Allow", "Principal": {"AWS": "arn:aws:iam::123456789012:root"}, "Action": "kms:*", "Resource": "*"}]}
| 要素 | 値 | 意味 |
|---|---|---|
| Sid | Enable IAM User Permissions | このステートメントの名前(説明用) |
| Effect | Allow | 許可する |
| Principal | arn:aws:iam::123456789012:root | 許可する相手。:root はアカウント全体を表す |
| Action | kms:* | KMS のすべての操作 |
| Resource | * | このキーポリシーが付いているキー自身 |
AWS KMS デベロッパーガイドによると、この「Enable IAM User Permissions」のステートメントには次の役割があります。
- Principal の :root は、アカウントとその管理者(ルートユーザーを含む)を表す。KMS のキーポリシーは、このような明示的なステートメントがないと、アカウントやその中の ID に自動では権限を与えない
- このステートメントがあることで、キーポリシーに加えて IAM ポリシーでもキーへのアクセスを許可できるようになる。これがないと、キーへのアクセスを許可する IAM ポリシーは効かない
- 管理者にアクセス制御の権限が残るため、キーが管理できなくなる事態を避けられる
特定のロールにキーの利用を許可する
アプリケーション用のロール(app-role)に、暗号化・復号・データキー生成を許可するステートメントを足した例です(policy.json)。
{
"Version": "2012-10-17",
"Id": "key-policy-demo",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "AllowAppToUseKey",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:role/app-role"},
"Action": ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey", "kms:DescribeKey"],
"Resource": "*"
}
]
}
aws kms put-key-policy --key-id $KEY_ID --policy-name default --policy file://policy.json
aws kms get-key-policy --key-id $KEY_ID --policy-name default --query Policy --output text
put-key-policy はポリシー全体を置き換えるので、1つ目のステートメントも含めて渡します。--policy-name は default を指定します。
| Action | 許可される操作 | 使う場面 |
|---|---|---|
| kms:Encrypt | encrypt | 小さなデータを直接暗号化する |
| kms:Decrypt | decrypt | 暗号文や、暗号化されたデータキーを戻す |
| kms:GenerateDataKey | generate-data-key | エンベロープ暗号化で暗号化する側 |
| kms:DescribeKey | describe-key | キーの状態や種類を確認する |
| kms:ReEncryptFrom / kms:ReEncryptTo | re-encrypt | 別のキーで暗号化し直す |
復号だけを行うアプリケーションには kms:Decrypt だけ、暗号化もする側には kms:GenerateDataKey や kms:Encrypt を足す、というように役割で分けられます。デベロッパーガイドのコンソール既定ポリシーでは、キーを使う側(key users)に kms:Encrypt、kms:Decrypt、kms:ReEncrypt*、kms:GenerateDataKey*、kms:DescribeKey がまとめて許可されています。
キーのローテーションと後片付け
自動ローテーションを有効にする
aws kms enable-key-rotation --key-id $KEY_ID
aws kms get-key-rotation-status --key-id $KEY_ID
{
"KeyRotationEnabled": true
}
別のキーで暗号化し直す(re-encrypt)
平文を手元に出さずに、暗号文を別のキーで暗号化し直せます。
aws kms re-encrypt --ciphertext-blob fileb://secret.enc \
--destination-key-id $KEY2 --query '{Src:SourceKeyId,Dst:KeyId}'
{
"Src": "arn:aws:kms:ap-northeast-1:123456789012:key/435a4396-f31f-4d4a-ab54-5764e2d7b098",
"Dst": "arn:aws:kms:ap-northeast-1:123456789012:key/34f34401-6236-49d8-84c5-fc919a47cc44"
}
エイリアスを消し、キーの削除を予約する
KMS キーはすぐには消えず、待機期間を置いて削除されます。API リファレンスの説明では、待機期間(--pending-window-in-days)は7〜30日で、指定しない場合は30日です。
aws kms delete-alias --alias-name alias/demo-app
aws kms schedule-key-deletion --key-id $KEY_ID --pending-window-in-days 7
aws kms describe-key --key-id $KEY_ID --query 'KeyMetadata.[KeyState,Enabled]' --output text
PendingDeletion False
待機期間のうちなら取り消せます。取り消したあとのキーは Disabled(無効)の状態になるので、使うには enable-key で有効に戻します。
aws kms cancel-key-deletion --key-id $KEY_ID
aws kms describe-key --key-id $KEY_ID --query 'KeyMetadata.[KeyState,Enabled]' --output text
Disabled False
キーを削除すると、そのキーで暗号化したデータは復号できなくなります。 削除を予約する前に、そのキーで暗号化したデータが残っていないかを確かめます。
よくある質問
Q. encrypt と generate-data-key はどう使い分けますか? A. 4096バイトまでの小さなデータ(パスワードやトークン)は encrypt、それより大きいデータやファイルは generate-data-key でデータキーを作り、手元で暗号化します。
Q. decrypt で InvalidCiphertextException が出ます。どこを見ればよいですか? A. 暗号文を base64 -d でバイナリに戻して保存したか、--ciphertext-blob に fileb:// を使っているか、暗号化のときと同じ EncryptionContext を渡しているか、の3点を確かめます。
Q. boto3 でも Base64 の変換は必要ですか? A. 不要です。Plaintext も CiphertextBlob も bytes でそのまま受け渡しします。文字列として保存したいときだけ、自分で base64 モジュールを使います。
Q. 平文のデータキーは保存してよいですか? A. 保存しません。暗号化や復号に使ったらすぐに消し、保存するのは暗号化されたデータキー(CiphertextBlob)だけにします。必要になったら decrypt で平文に戻せます。
まとめ
- KMS のキーは外に出てこない。encrypt / decrypt で暗号化と復号を KMS に頼む形で使う
- encrypt は4096バイトまで。超えると ValidationException になるので、データキーを使う方法に切り替える
- CLI は「入力は fileb://、出力は base64 -d」。boto3 は bytes をそのまま受け渡しする
- エンベロープ暗号化は generate-data-key → 手元でAES暗号化 → 暗号化されたデータキーと一緒に保存。平文のデータキーは残さない
- InvalidCiphertextException は、暗号文の破損と EncryptionContext の不一致の両方で出る
- キーポリシーは Principal(誰に)と Action(何を)で読む。復号だけの側には kms:Decrypt だけを許可できる
- キーの削除は7〜30日の待機期間つき。削除したキーで暗号化したデータは復号できなくなる
参考・出典
- AWS KMS API Reference「Decrypt」「GenerateDataKey」(docs.aws.amazon.com/kms/latest/APIReference/)
- AWS KMS Developer Guide「Default key policy」「AWS KMS permissions」(docs.aws.amazon.com/kms/latest/developerguide/)
- AWS CLI User Guide「Specifying parameter values(Blob)」「Migration guide for the AWS CLI version 2」(docs.aws.amazon.com/cli/latest/userguide/)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「AWS KMSの使い方|CLIとboto3で暗号化・復号・データキー」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。