検証で理解する、AWS KMSエンベロープ暗号化とBYOKの仕組み

記事タイトルとURLをコピーする

はじめに

クラウドリライアビリティ2課 シノハラです。

AWS Certified Security - Specialty(SCS)の試験勉強でKMSを学習していた際、エンベロープ暗号化の概念は理解できても、数多くの「〇〇キー」の具体的な依存関係がピンと来ませんでした。

この混乱の原因は、AWSが裏で処理してくれる「透過性」ゆえに、裏側の仕組みが隠されてしまうというジレンマにあると考えています。

そこでこの記事では、あえてキーマテリアルを削除する(意図的に壊す)という検証を行います。正常な状態を眺めるだけでなく、あえてトラブルを起こしてエラーを観察することで、各用語の意味と鍵の依存関係を体験的にクリアにしたいと思います。

概要

この記事では、AWS KMSの外部キーマテリアル(BYOK)インポートから、鍵の消去・復旧における挙動の検証内容をまとめています。

  • KMSキー(KMS key) - そもそもどんな役割?
  • キーマテリアル(Key Material) - KMSキーとは別物?
  • データキー(Data Key) - どこに保存される?
  • ラッピングキー(Wrapping Key) - 何をラップするの?
  • インポートトークン(Import Token) - なぜ必要?

この記事で学べること:

  • KMSキーとキーマテリアルの関係(「器と中身」の違い)
  • データキーの保存場所とその仕組み
  • ラッピングキーとインポートトークンの役割
  • エンベロープ暗号化における鍵の依存関係
  • キーマテリアルを削除した際の影響範囲
  • 同一キーマテリアルの再インポートによる復旧方法
  • BYOK(Bring Your Own Key)の実践的な実装

対象読者: AWS SCS試験の学習中、またはAWS KMSの仕組みを深く理解したいエンジニア

エンベロープ暗号化とは?基礎から理解する

エンベロープ暗号化の基本概念

エンベロープ暗号化(Envelope Encryption)とは、データを暗号化した鍵(データキー)を、さらに上位の鍵(KMSキー)で暗号化して保護する階層型の暗号化手法です。

AWS KMSにおける鍵の階層構造

AWS KMSでは、以下のような階層構造で鍵が管理されています:

graph TB
    subgraph KMS["【AWS KMS内部】"]
        ROOT["最上位:KMSキー(ルートキー)<br/>※内部のキーマテリアル(256ビット対称鍵)は外部へエクスポート不可"]
    end
    
    ROOT -->|データキーのみを暗号化(数バイト)| ENC_DK
    
    subgraph SERVICE["【各AWSサービス(データ保存場所)】"]
        DATA["データ本体<br/>(大容量:数GB〜数TB)"]
        ENC_DK["暗号化されたデータキー<br/>(小容量:数バイト)"]
    end
    
    ENC_DK -.->|復号された平文キーで制御| DATA
    
    style ROOT fill:#ffcccc
    style ENC_DK fill:#ccffcc
    style DATA fill:#ccccff
  • KMSキー:
    AWS上で暗号化を制御する「管理の単位」です。識別子(ARN)やアクセスポリシーなどのメタデータと、内部のキーマテリアルを包括した総称を指します。
    ※KMSキーには、AWSが自動作成する「AWS管理キー」と、ユーザー自身が作成する「カスタマー管理キー」の2種類があります。本記事の検証では、ユーザーが所有し操作権限を持つ「カスタマー管理キー」を使用します。
  • キーマテリアル:
    KMSキーの内部(専用ハードウェア:HSM)に格納されている、暗号計算を行う「256ビットの対称鍵データ(実体)」です。これが存在して初めてKMSキーは機能します。
  • データキー:
    実際のデータ本体を暗号化・復号するために、KMSキーから一時的に生成される「実行用の鍵(子鍵)」です。暗号化された状態でデータ本体の近く(Amazon S3ならオブジェクトのメタデータ、Amazon Elastic Block Store(EBS)ならボリュームのメタデータなど)に保存されます。

なぜ二重に暗号化するのか?

「1回暗号化すれば十分では?」と思うかもしれませんが、これには実務上の大きなメリットがあります。

メリット 説明
ルートキーの保護 暗号化の実体である「キーマテリアル」がAWS KMSの内部から外に出ない構造になっているため、最上位の鍵(ルートキー)が物理的に流出するリスクを完全に排除できます。
鍵管理の効率化 暗号化されたデータキーをデータ本体のすぐ横に同梱して保存できるため、数百万規模の鍵を個別に管理・紐付けする独立したデータベースが不要になります。
パフォーマンスの向上 鍵のローテーション(変更)時に、大容量のデータ本体(数GB〜数TB)を再暗号化する必要がありません。KMS側から届く「暗号化されたデータキー(数バイト)」だけを再暗号化すれば済むため、インフラの処理負荷を最小限に抑えられます。

💡 本記事での検証対象 今回の検証では、Amazon S3のSSE-KMS(AWS KMSを使用したサーバー側の暗号化)を使用します。Amazon S3では暗号化されたデータキーがオブジェクトのメタデータに保存されますが、他のサービス(Amazon Elastic Block Store(EBS)、Amazon Relational Database Service(RDS)、Amazon DynamoDBなど)でもKMSとの連携の基本原理は同じです。

S3におけるSSE-KMSの動作フロー

鍵を破壊した際のエラー原因を深く理解するために、まずは通常時(正常時)にS3とAWS KMSの間でどのようなAPI通信が行われているのか、裏側のフローを確認します。

sequenceDiagram
    participant S3 as S3バケット
    participant KMS as AWS KMS
    
    Note over S3,KMS: 【暗号化フロー(データ保存時)】
    S3->>KMS: ① GenerateDataKey APIを呼び出し
    Note over KMS: 内部のHSMでキーマテリアルを使用し、<br/>平文と暗号化版の「データキー」を生成
    KMS->>S3: ② 平文データキー + 暗号化データキー を返却
    S3->>S3: ③ 平文データキーでデータ本体を暗号化
    S3->>S3: ④ メモリから平文データキーを即座に削除
    S3->>S3: ⑤ 暗号化データキーをS3のメタデータに保存
    
    Note over S3,KMS: 【復号フロー(データ取得時)】
    S3->>S3: ① メタデータから暗号化データキーを取得
    S3->>KMS: ② Decrypt APIを呼び出し(暗号化データキーを送信)
    Note over KMS: 内部のHSMでキーマテリアルを使用し、<br/>データキーを平文に復号
    KMS->>S3: ③ 平文データキー を返却
    S3->>S3: ④ 平文データキーでデータ本体を復号
    S3->>S3: ⑤ メモリから平文データキーを即座に削除

暗号化時:

① GenerateDataKey APIを呼び出し
S3がKMSに対し、ファイルを暗号化するためのデータキーの発行をリクエストします。

② 平文データキー + 暗号化データキー を返却
KMSが内部(HSM内)のキーマテリアルを使って「平文のデータキー」と「暗号化されたデータキー」を生成し、S3に返します。

③ 平文データキーでデータ本体を暗号化
S3が、受け取った生の「平文データキー」を使って、アップロードされたデータ本体を暗号化します。

④ メモリから平文データキーを即座に削除
暗号化完了後、安全のため、S3は自身のメモリ上から生の「平文データキー」を完全に消去します。

⑤ 暗号化データキーをS3のメタデータに保存
KMSによってロックされている「暗号化されたデータキー」だけを、暗号化データのすぐ横(メタデータ内)に同梱して保存します。

復号時:

① メタデータから暗号化データキーを取得
ユーザーからダウンロード要求を受けると、S3は対象データのすぐ横に同梱されている「暗号化されたデータキー」を取り出します。

② Decrypt APIを呼び出し
S3は、取り出した「暗号化されたデータキー」をKMSに送信し、復号をリクエストします。

③ 平文データキー を返却
KMSが内部のキーマテリアルを使ってデータキーを元の生の鍵に戻し、S3に返却します。

④ 平文データキーでデータ本体を復号
S3が、届いた生の「平文データキー」を使って、暗号化されているデータ本体を元の平文データに復元します。

⑤ メモリから平文データキーを即座に削除
復号完了後、S3は自身のメモリ上から生の「平文データキー」を即座に消去し、復号された平文データだけをユーザーに渡します。

重要なポイント:

  • 平文の鍵は残らない(暗号化の実行用):
    データをその場で暗号化・復号する「実行処理」のためだけに、生の「平文データキー」が一瞬だけメモリ上に現れます。処理が完了すると即座に消去され、ストレージに永続保存されることはありません。
  • すべての鍵の命はKMS内部にある(将来の復号・保管用):
    将来の復号に備えてS3側(メタデータ)に同梱・保存されるのは、親鍵でロックされた「暗号化されたデータキー」のみです。そのため、KMS内部のキーマテリアルが利用できない状態になると、この暗号化されたデータキーを平文に戻せなくなるため、データの復号は100%不可能になります。

💡 検証に向けた補足 通常のKMSキーでは、この「KMS内部のキーマテリアル」をユーザーが手動で削除して挙動を確かめることができません。そこで今回の検証では、鍵の即時削除が可能な「BYOK」という機能を利用します。検証に進む前に、BYOKの概要について整理します。

BYOK(Bring Your Own Key)とは?

基本概念と通常のKMSキーとの違い

BYOKとは、AWSにキーマテリアル(鍵の実体データ)の生成を委ねず、ユーザー自身が自社環境等で生成したキーマテリアルをAWS KMSにインポートして利用する機能です。

厳格なコンプライアンス対応や、鍵の所有権を完全にコントロールしたい運用要件で選択されます。通常のKMSキー(AWSがキーマテリアルを生成する鍵)とBYOKには、以下の違いがあります。

項目 通常のKMSキー BYOK(インポート鍵)
キーマテリアルの生成元 AWSが自動生成 ユーザーが生成・インポート
キーマテリアルの削除 7-30日の待機期間 即座に削除可能
自動ローテーション AWSが全自動で実行 サポート対象外(手動で再インポートが必要)
有効期限設定 不可 設定可能(期限切れで自動的に削除される)
再インポート 対象外 可能
鍵の耐久性・責任 AWSが担保 ユーザーがバックアップの全責任を負う

インポートに必要なラッピングキーとインポートトークン

生のキーマテリアルを安全にインターネット経由でAWSに送信するため、AWS KMSから以下の2つのデータをあらかじめ取得する必要があります。

この2つのパラメーターは、ローカル環境からAWS KMSへインポートする際に、以下のような流れで連携します。

graph TB
    subgraph Local["ローカル環境"]
        KEY["① キーマテリアル<br/>(平文)"]
        TOKEN["② インポートトークン<br/>(通行手形)"]
        ENC_KEY["暗号化された<br/>キーマテリアル"]
    end
    
    subgraph Network["ネットワーク転送"]
        TRANSFER["【2つをセットで送信】<br/>・暗号化キーマテリアル<br/>・インポートトークン"]
    end
    
    subgraph AWS["AWS KMS内部"]
        VERIFY["1. トークンを検証<br/>(対象キーの識別)"]
        UNWRAP["2. 秘密鍵で復号"]
        KMS_KEY["3. キーマテリアルを格納"]
    end
    
    KEY -->|ラッピングキーで暗号化| ENC_KEY
    ENC_KEY --> TRANSFER
    TOKEN --> TRANSFER
    TRANSFER --> VERIFY
    VERIFY --> UNWRAP
    UNWRAP --> KMS_KEY
    
    style KEY fill:#ffcccc
    style ENC_KEY fill:#ccffcc
    style KMS_KEY fill:#ccccff

ラッピングキー(Wrapping Key / 公開鍵)とは

役割: キーマテリアルを暗号化するための公開鍵

キーマテリアルは最上位の親鍵の実体であり、極めて機密性が高いため、平文(生データ)のままネットワーク上を送信することはできません。KMSが提供する一時的な公開鍵(対応する秘密鍵はKMS内部のみに存在)でカプセル化することで、安全に転送できます。

インポートトークン(Import Token)とは

役割: キーマテリアルのインポートを認証するための一時トークン(有効期限24時間)

暗号化されたキーマテリアルを送信するだけでは、AWS KMS側は「これはどのアカウントの、どのKMSキーに格納すべきデータなのか」を判断できません。インポートトークンという通行手形をセットで提出することで、以下が保証されます:

  • 正しいKMSキーへの紐付け - 特定のKMSキー専用であることを証明
  • リプレイ攻撃の防止 - ワンタイムトークンのため、過去の通信を傍受しても再利用不可
  • 時間制約による安全性 - 24時間の有効期限で、漏洩時の悪用リスクを最小化

今回の検証:キーマテリアルを削除してファイルへのアクセスを遮断する

検証の目的

通常時はAWSの全自動処理の裏に隠されている「エンベロープ暗号化」の仕様を、あえてキーマテリアルを削除することで確かめます。

最上位の鍵であるKMSキー(親鍵)の実体であるキーマテリアルを削除した瞬間に、S3オブジェクトの復号(データキーの解除)がどう連動して失敗するのかを観察し、各用語の意味と依存関係を体験的に理解することが目的です。

この検証では、以下を実際に体験することで、エンベロープ暗号化の仕組みを深く理解します:

  1. 自作したキーマテリアルをAWS KMS(カスタマー管理キー)にインポート(BYOK)
  2. インポートしたKMSキーを使い、S3オブジェクトをSSE-KMSで暗号化して保存
  3. キーマテリアルを即時削除し、暗号化されたファイルへのアクセスを遮断する
  4. 発生したエラーメッセージから、キーマテリアルとデータキーの正確な依存関係を分析
  5. 同じキーマテリアルを再インポートし、暗号化されていたファイルを無傷で復活させる

検証の流れ

graph TB
    subgraph Phase1["【1. 鍵の準備とインポート】"]
        S1["Step 1: キーマテリアル生成"] --> S2["Step 2: KMSキーの作成<br/>(キーマテリアルなし)"]
        S2 --> S3["Step 3: キーマテリアルを<br/>インポートする準備"]
    end
    
    subgraph Phase2["【2. S3での暗号化運用】"]
        S3 --> S4["Step 4: S3バケット作成 /<br/>SSE-KMS 設定"]
        S4 --> S5["Step 5: テストファイルを<br/>アップロード"]
        S5 --> S6["Step 6: ファイルのダウンロードを<br/>確認(正常動作) ✓"]
    end
    
    subgraph Phase3["【3. 鍵の削除とエラー分析】"]
        S6 --> S7["Step 7: キーマテリアルを削除 🔥"]
        S7 --> S8["Step 8: ファイルのダウンロードを<br/>試行(エラー発生!) ✗"]
        S8 --> S9["Step 9: エラーメッセージから<br/>鍵の依存関係を理解"]
    end
    
    subgraph Phase4["【4. 再インポートとデータ復活】"]
        S9 --> S10["Step 10: キーマテリアルを<br/>再インポート"]
        S10 --> S11["Step 11: ファイルのダウンロードを<br/>再試行(復活!) ✓"]
    end
    
    style S7 fill:#ff9999
    style S8 fill:#ff9999
    style S6 fill:#99ff99
    style S11 fill:#99ff99

検証のポイント:

  • Step 7でキーマテリアルを削除し、暗号化されたデータへのアクセスを遮断
  • Step 8でエラーが発生し、エンベロープ暗号化の依存関係が明確になる
  • Step 10で同じキーマテリアルを再インポートし、データが復活することを確認

前提条件

  • 必要な環境: AWS CLI(v2.x以上)、OpenSSL(鍵生成用)
  • 必要なIAM権限: KMSの作成・インポート・削除権限、およびS3の操作権限

実装手順

【1. 鍵の準備とインポート】

Step 1: キーマテリアル生成

まず、ローカル環境でOpenSSLを使って256ビットのキーマテリアルを生成します。

# 256ビット(32バイト)のランダムなキーマテリアルを生成
openssl rand -out key-material.bin 32

# 生成された鍵のサイズを確認
ls -lh key-material.bin
# 出力: -rw-r--r-- 1 user user 32 Jun 6 12:00 key-material.bin

ポイント:

  • AWS KMSの対称暗号化鍵には256ビット(32バイト)のキーマテリアルが必要
  • openssl randは暗号学的に安全な乱数を生成

Step 2: KMSキーの作成(キーマテリアルなし)

次に、キーマテリアルを持たないKMSキーを作成します。

# KMSキーを作成(キーマテリアルは「まだ」インポートしない)
aws kms create-key \
    --origin EXTERNAL \
    --description "BYOK Experiment Key - DO NOT USE IN PRODUCTION" \
    --region ap-northeast-1

# 出力例(KeyIdを記録しておく)
# {
#     "KeyMetadata": {
#         "KeyId": "12345678-1234-1234-1234-123456789012",
#         "KeyState": "PendingImport",
#         "Origin": "EXTERNAL",
#         ...
#     }
# }

KeyIdを環境変数に保存:

export KEY_ID="12345678-1234-1234-1234-123456789012"

ここで何が起こったか?

このコマンドで作成されるのは、キーマテリアルを持たないKMSキーです。

作成されるもの 作成されないもの
✅ KMSキーのARN ❌ キーマテリアル(暗号化の実体)
✅ メタデータ(Alias、Policyなど) ❌ 暗号化・復号の機能
✅ キーマテリアルを受け入れる「器」 ❌ 実際に使える鍵

比喩で理解する:

今の状態 = 「家は建ったが、鍵がまだない状態」

家(KMSキー)  → ✅ 存在する(住所もある)
鍵(キーマテリアル)→ ❌ まだ作っていない

→ 家に入れない = 暗号化・復号できない

確認ポイント:

  • Origin: EXTERNAL - この鍵は外部からキーマテリアルをインポートする設定
  • KeyState: PendingImport - キーマテリアルがまだインポートされていない状態
  • この状態では、EncryptDecryptGenerateDataKey などの操作は全て失敗する

試しに暗号化してみる(失敗するはず):

# この時点で暗号化を試みると...
echo "test" | aws kms encrypt \
    --key-id $KEY_ID \
    --plaintext fileb:///dev/stdin \
    --region ap-northeast-1

# エラーが発生:
# An error occurred (KMSInvalidStateException) when calling the Encrypt operation:
# arn:aws:kms:...:key/12345678-... is pending import.

キーマテリアルがないため、何もできません。次のステップでキーマテリアルをインポートします。

エイリアスを作成(オプション):

aws kms create-alias \
    --alias-name alias/byok-experiment \
    --target-key-id $KEY_ID \
    --region ap-northeast-1

Step 3: キーマテリアルをインポートする準備

KMSから、キーマテリアルをインポートするためのパラメータを取得します。

# インポート用パラメータを取得
aws kms get-parameters-for-import \
    --key-id $KEY_ID \
    --wrapping-algorithm RSAES_OAEP_SHA_256 \
    --wrapping-key-spec RSA_2048 \
    --region ap-northeast-1 \
    --output json > import-params.json

# ラッピングキー(公開鍵)とインポートトークンを抽出
cat import-params.json | jq -r '.PublicKey' | base64 -d > wrapping-key.pub
cat import-params.json | jq -r '.ImportToken' | base64 -d > import-token.bin

キーマテリアルを暗号化:

# OpenSSLでキーマテリアルを公開鍵で暗号化
openssl pkeyutl \
    -encrypt \
    -in key-material.bin \
    -out encrypted-key-material.bin \
    -inkey wrapping-key.pub \
    -keyform DER \
    -pubin \
    -pkeyopt rsa_padding_mode:oaep \
    -pkeyopt rsa_oaep_md:sha256

# 暗号化されたキーマテリアルを確認
ls -lh encrypted-key-material.bin
# 出力: -rw-r--r-- 1 user user 256 Jun 6 12:05 encrypted-key-material.bin

キーマテリアルをインポート:

aws kms import-key-material \
    --key-id $KEY_ID \
    --encrypted-key-material fileb://encrypted-key-material.bin \
    --import-token fileb://import-token.bin \
    --expiration-model KEY_MATERIAL_DOES_NOT_EXPIRE \
    --region ap-northeast-1

# インポート成功の確認
aws kms describe-key \
    --key-id $KEY_ID \
    --region ap-northeast-1 \
    --query 'KeyMetadata.KeyState' \
    --output text
# 出力: Enabled

確認ポイント: - KeyStatePendingImportからEnabledに変わっている - これでキーマテリアルが正常にインポートされ、使用可能になった

【2. S3での暗号化運用】

Step 4: S3バケット作成 / SSE-KMS 設定

インポートしたKMSキーを使ってS3オブジェクトを暗号化します。

# バケット名を設定(グローバルにユニークな名前が必要)
export BUCKET_NAME="byok-experiment-$(date +%s)"

# S3バケットを作成
aws s3 mb s3://$BUCKET_NAME --region ap-northeast-1

# バケットのデフォルト暗号化を設定
aws s3api put-bucket-encryption \
    --bucket $BUCKET_NAME \
    --server-side-encryption-configuration '{
        "Rules": [{
            "ApplyServerSideEncryptionByDefault": {
                "SSEAlgorithm": "aws:kms",
                "KMSMasterKeyID": "'"$KEY_ID"'"
            },
            "BucketKeyEnabled": false
        }]
    }' \
    --region ap-northeast-1

重要:BucketKeyEnabled: falseの理由

Amazon S3 Bucket Key(以降、S3 Bucket Key)を有効にすると、複数のオブジェクトで同じデータキーを再利用するため、今回の実験の観察がしづらくなります。無効にすることで、各オブジェクトが独自のデータキーを持つようになります。

Step 5: テストファイルをアップロード

# テストファイルを作成
echo "This is a secret message encrypted with BYOK KMS key!" > secret.txt

# S3にアップロード(自動的にKMSキーで暗号化される)
aws s3 cp secret.txt s3://$BUCKET_NAME/secret.txt --region ap-northeast-1

# アップロード成功
# upload: ./secret.txt to s3://byok-experiment-1234567890/secret.txt

暗号化の確認:

# オブジェクトのメタデータを確認
aws s3api head-object \
    --bucket $BUCKET_NAME \
    --key secret.txt \
    --region ap-northeast-1 \
    --query '{Encryption: ServerSideEncryption, KeyId: SSEKMSKeyId}' \
    --output json

出力例:

{
    "Encryption": "aws:kms",
    "KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}

ここで何が起こったか?

  1. S3がKMSにGenerateDataKey APIを呼び出し
  2. KMSがデータキーを生成し、インポートしたKMSキーで暗号化
  3. S3が平文データキーでファイルを暗号化
  4. S3が暗号化されたデータキーをオブジェクトのメタデータに保存
  5. S3がメモリから平文データキーを削除

Step 6: ファイルのダウンロードを確認(正常動作)

# ファイルをダウンロード
aws s3 cp s3://$BUCKET_NAME/secret.txt downloaded-secret.txt --region ap-northeast-1

# 内容を確認
cat downloaded-secret.txt
# 出力: This is a secret message encrypted with BYOK KMS key!

正常にダウンロードできました!

ここで何が起こったか?

  1. S3がオブジェクトのメタデータから暗号化されたデータキーを取得
  2. S3がKMSにDecrypt APIを呼び出し、暗号化されたデータキーを送信
  3. KMSがインポートされたキーマテリアルを使ってデータキーを復号
  4. S3が平文データキーでファイルを復号
  5. S3がメモリから平文データキーを削除

【3. 鍵の破壊とエラー分析】

Step 7: キーマテリアルを削除 🔥

さて、いよいよ本検証のメインイベントです。キーマテリアルを削除して、暗号化されたファイルへのアクセスを遮断します。

# キーマテリアルを削除
aws kms delete-imported-key-material \
    --key-id $KEY_ID \
    --region ap-northeast-1

# 削除成功(出力なし)

KMSキーの状態を確認:

aws kms describe-key \
    --key-id $KEY_ID \
    --region ap-northeast-1 \
    --query 'KeyMetadata.{State: KeyState, Origin: Origin, Enabled: Enabled}' \
    --output json

出力:

{
    "State": "PendingImport",
    "Origin": "EXTERNAL",
    "Enabled": false
}

重要な変化: - KeyStateEnabledからPendingImportに戻った - Enabledtrueからfalseに変わった - しかし、KMSキー自体は削除されていない(メタデータは残っている)

Step 8: ファイルのダウンロードを試行(エラー発生!)

キーマテリアルを削除した状態で、暗号化されたファイルにアクセスしてみましょう。

# ファイルのダウンロードを試行
aws s3 cp s3://$BUCKET_NAME/secret.txt downloaded-secret-2.txt --region ap-northeast-1

エラー発生:

download failed: s3://byok-experiment-1234567890/secret.txt to ./downloaded-secret-2.txt An error occurred (KMS.KMSInvalidStateException) when calling the GetObject operation: 
arn:aws:kms:ap-northeast-1:123456789012:key/12345678-1234-1234-1234-123456789012
is pending import.

ダウンロード失敗!

Step 9: エラーメッセージから鍵の依存関係を理解

このエラーメッセージから、キーマテリアル、KMSキー、データキーの関係を理解していきます。

エラーの全体像
download failed: s3://byok-experiment-1234567890/secret.txt to ./downloaded-secret-2.txt 
An error occurred (KMS.KMSInvalidStateException) when calling the GetObject operation: 
arn:aws:kms:ap-northeast-1:123456789012:key/12345678-1234-1234-1234-123456789012
is pending import.
段階的に分解して理解する

① S3側で何が起こったか

処理内容 結果 状態
S3オブジェクト本体を取得 データ取得成功 ✅ 成功
メタデータから暗号化されたデータキーを取得 暗号化データキー取得成功 ✅ 成功
KMSにDecryptリクエスト送信 リクエスト送信成功 ✅ 成功
KMSからの応答を受信 「キーマテリアルがないので復号できません」 ❌ エラー

S3側の視点:

  • S3の手元には、ファイル本体も、ロックされた子鍵(暗号化データキー)もすべて揃っています。問題はここから先のKMS側にあります。

② KMS側で何が起こったか

S3からKMSへDecryptリクエストが送られた後の処理:

処理内容 結果 状態
KMSキーのメタデータ確認 ARN、Alias等が存在 ✅ 成功
KeyStateを確認 PendingImport(使用不可状態) ❌ 失敗
キーマテリアル取得試行 キーマテリアルが存在しない ❌ 失敗
エラー返却 KMSInvalidStateException: is pending import ❌ エラー
S3への応答 GetObject操作が失敗 ❌ エラー

KMS側の視点:

  • KMSキーという「器(管理情報)」は確かに存在しています。しかし、暗号処理を行うための「実体(キーマテリアル)」が空っぽであるため、子鍵(データキー)を復号してあげることができません。
視覚的な理解:何が残っていて、何が失われたのか

上記の処理フローを時系列で図に表すと、以下のようになります。器(緑)は無傷ですが、中身(赤)が消えたことで、最終的にデータが引き渡せない連鎖が起きていることが分かります。

graph TD
    subgraph S3["【1. S3オブジェクト(手元にある)】"]
        S3_DK["✅ 暗号化されたデータキー<br/>(メタデータに同梱)"]
        S3_DATA["🔒 暗号化されたデータ本体<br/>(復号待ち)"]
    end

    subgraph KMS["【2. AWS KMS内部の状況】"]
        KMS_META["✅ KMSキーのメタデータ<br>(器はある)<br/>ステータス: PendingImport"]
        KMS_MAT["❌ キーマテリアル<br/>(中身は削除済み!)"]
    end

    subgraph RESULT["【3. 最終結果】"]
        FAIL["❌ 平文データキーが<br/>手に入らない"]
        DATA_FAIL["🚫 ファイルを復号できない<br/>(AccessDenied)"]
    end

    %% 上から下への依存関係フロー
    S3_DK -->|① 復号リクエストを送信| KMS_META
    KMS_META -->|② 鍵の実体を取得しようとするが...| KMS_MAT
    KMS_MAT -->|③ 復号失敗| FAIL
    FAIL -->|④ 生の鍵がないため| DATA_FAIL

    %% スタイリング
    style KMS_MAT fill:#ff9999,stroke:#ff3333,stroke-width:2px
    style FAIL fill:#ff9999,stroke:#ff3333,stroke-width:2px
    style DATA_FAIL fill:#ffcccc,stroke:#ff3333
    
    style S3_DK fill:#ccffcc,stroke:#33cc33
    style KMS_META fill:#ccffcc,stroke:#33cc33
    style S3_DATA fill:#f5f5f5,stroke:#aaaaaa
この検証から明確になった「鍵の依存関係」の本質

このエラーから、エンベロープ暗号化の決定的なルールが浮かび上がります。それは、「中身(キーマテリアル)を失った親鍵は、完全に機能停止する」ということです。

今回は「既存ファイルの復号(ダウンロード:Step8)」でエラーが発生しましたが、キーマテリアルが空っぽになったKMSキーは、開くこと(復号)だけでなく、新しく暗号化する操作もすべて連動して全滅します。

  • 新しいファイルが保存できなくなる(GenerateDataKeyの失敗)
    もしStep 7(削除)の後に、もう一度「Step 5」のように新しいファイルをS3にアップロードしようとしても、S3側が暗号化用の子鍵(データキー)を新しく発行できなくなるため、アップロード操作そのものがエラーになります。

  • KMSを使った直接の暗号化(Encryptの失敗)
    S3を介さずに、AWS CLIなどで直接KMSキーを使ってデータを暗号化しようとする操作も、実体がないためすべて拒否されます。

つまり、キーマテリアルを削除した瞬間、KMSキーはARNという「名前」だけを残して、新しいデータを守ることも、過去のデータを戻すことも一切できない「完全な抜け殻」になるのです。

【4. 再インポートとデータ復活】

Step 10: キーマテリアルを再インポート

BYOKの大きなメリットの1つは、キーマテリアルを削除しても、同じものを再インポートすればデータが復活することです。

# Step 3で保存したキーマテリアルがまだ手元にあることを確認
ls -lh key-material.bin encrypted-key-material.bin import-token.bin

# インポートトークンの有効期限を確認(24時間で期限切れ)
# 期限切れの場合は、Step 3を再実行してパラメータを再取得

インポートトークンが有効な場合:

# キーマテリアルを再インポート
aws kms import-key-material \
    --key-id $KEY_ID \
    --encrypted-key-material fileb://encrypted-key-material.bin \
    --import-token fileb://import-token.bin \
    --expiration-model KEY_MATERIAL_DOES_NOT_EXPIRE \
    --region ap-northeast-1

KMSキーの状態を確認:

aws kms describe-key \
    --key-id $KEY_ID \
    --region ap-northeast-1 \
    --query 'KeyMetadata.{State: KeyState, Enabled: Enabled}' \
    --output json

出力:

{
    "State": "Enabled",
    "Enabled": true
}

KMSキーが復活しました!

Step 11: ファイルのダウンロードを再試行(復活!)

# ファイルのダウンロードを再試行
aws s3 cp s3://$BUCKET_NAME/secret.txt downloaded-secret-3.txt --region ap-northeast-1

# 内容を確認
cat downloaded-secret-3.txt
# 出力: This is a secret message encrypted with BYOK KMS key!

ダウンロード成功!ファイルが復活しました!

なぜ復活できたのか?

  1. 元のキーマテリアル(同じ256ビットのデータ)を再インポートした
  2. KMSが同じキーマテリアルを使ってデータキーを復号できるようになった
  3. S3が復号されたデータキーでオブジェクトを復号できるようになった

重要なポイント:

  • S3に保存されている暗号化されたデータキーは変更されていない
  • 再インポートしたキーマテリアルが元と完全に同じであれば、復号できる
  • もし違うキーマテリアルをインポートすると、復号は永遠に失敗する

まとめ

この記事では、AWS KMSのキーマテリアルを意図的に削除・復旧する検証を通じて、エンベロープ暗号化の仕組みを体験的に理解しました。

検証で明らかになった6つの鍵概念

1. KMSキーとキーマテリアルの関係

結論:別物ですが、「器と中身」の不可分な関係です。

  • KMSキー = 管理情報(ARN、ポリシー)を持つ「器」
  • キーマテリアル = 暗号計算を行う「中身」(実体)
  • Step 7で中身を削除すると、器は残っても機能停止(PendingImport状態)

2. データキーの保存場所

結論:KMS内部ではなく、データ本体(S3)のメタデータに同梱されます。

  • 暗号化された状態でS3オブジェクトと一緒に保存
  • キーマテリアル削除後もS3側には残り続ける
  • しかし復号できないため、データにアクセス不可

3. ラッピングキーの役割

結論:インポートする「生のキーマテリアル」を暗号化します。

  • インターネット経由でAWSに送信する際の安全性を確保
  • KMSが提供する一時的な公開鍵で暗号化
  • 対応する秘密鍵はKMS内部のみに存在

4. インポートトークンの必要性

結論:正しいKMSキーへの紐付けを証明する「通行手形」です。

  • 24時間の有効期限で不正な再利用を防止
  • どのKMSキーに対するインポートかを識別
  • ラッピングキーとセットで機能

5. エンベロープ暗号化の依存関係

結論:親鍵(キーマテリアル)が失われると、子鍵(データキー)も機能停止します。

  • 既存データの復号が不可能になる(Step 8で確認)
  • 新規データの暗号化(GenerateDataKey)も失敗
  • 双方向の暗号操作がすべて連動停止

6. キーマテリアルの再インポートによる復旧

結論:同一のキーマテリアルを再インポートすれば、データは完全に復活します。

  • S3の暗号化データキーは削除されていない(Step 10-11で確認)
  • 元と完全に同じキーマテリアルであれば復号可能
  • 異なるキーマテリアルでは永遠に復号不可
  • BYOKはバックアップ管理が生命線

BYOKの強みとリスク

強み:即時無効化

  • 通常のKMSキー削除:7〜30日の待機期間が必要
  • BYOKキーマテリアル削除:即座に実行可能
  • 緊急時のデータ遮断に極めて有効

リスク:完全な自己責任

  • 同じキーマテリアルを再インポートすれば復旧可能(Step 10〜11で実証)
  • しかし、バックアップを紛失すると永久に復号不可
  • 鍵の管理責任を100%自社で負う

学びのポイント

この検証を通じて、AWSが普段完璧に隠している「透過的な暗号化」の裏側を理解できました。

  • 正常時だけでなく、意図的に壊すことで依存関係が明確になる
  • 各用語の抽象的な説明ではなく、実際のエラーから体験的に学べる
  • AWS SCS試験の理解だけでなく、実務での鍵管理設計にも活かせる知識

参考リンク

AWS公式ドキュメント

篠原 佑弥

クロスインダストリー第1部 クラウドリライアビリティ2課

1日10,000歩、1,000kcal消費が日課