本記事は
ブログ書き初めウィーク
4日目の記事です。
📝
3日目
▶▶ 本記事 ▶▶
5日目
📅

1. はじめに
はじめまして。
先日、炊飯器の内蓋をつけ忘れてご飯を炊いてしまった新米社員の久保田です。

本記事では、AWS 初心者の私が「boto3」を使って AWS KMS(以下 KMS )の暗号化と復号を実際に試した内容をまとめています。 KMS の基本概念や、直接暗号化とエンベロープ暗号化の流れ、そして Python コードによる実装例を初心者目線で整理しました。
「boto3 って何者?」「暗号化って難しそう…」と思っている方や、boto3 を触ってみたい方にとって少しでも参考になれば幸いです。
2. AWS の操作方法
まず、「boto3」が何者かを知るために AWS の3つの操作方法を押さえておきます。
2.1. AWS マネジメントコンソール
AWS が提供するサービスを Web ブラウザ上で操作できる GUI(グラフィカルユーザインターフェース)です。

メリットとデメリットをまとめてみました。

プログラミングやコマンドの知識が不要で、ボタンのクリックやテキスト入力などの直感的な操作だけで簡単に設定できるため、最も初心者向きの操作方法です。
2.2. AWS CLI(Command Line Interface)
その名の通り、コマンド入力によって操作する方法です。
例:AWS CLI で S3バケットの一覧を表示
$ aws s3 ls
メリットとデメリットは次の通りです。

スクリプトによって繰り返し、一括操作が簡単なこと、またマネジメントコンソールでは実現できない詳細な設定が可能となる点が魅力です。 コマンド入力に慣れていて、開発や運用で AWS を頻繁に使う人に向いている操作方法になります。
2.3. AWS SDK(Software Development Kit)
AWS が提供するサービスをプログラムから操作するためのライブラリです。
例:boto3 を使用して S3 バケットの一覧を表示
import boto3 s3 = boto3.client('s3') response = s3.list_buckets(MaxBuckets=100)
メリットとデメリットを確認してみます。

SDK を使うことで、AWS サービスと高度に連携した複雑な処理やアプリケーション開発が容易になり、様々な言語で利用できるのも大きな魅力です。
また、コードから直接 AWS を操作できるため、手動操作よりも効率的で再現性の高い環境構築が可能になります。
SDK は様々な言語で提供されていますが、Python 用の SDK だけ boto3 という特別な名前がついているようです。 アマゾン川に生息する アマゾンカワイルカ (通称:boto)が由来で、Amazon との関連性を持たせつつ、短くて珍しい名前としてピッタリだったとか...
今回は、こちらの boto3🐬 を使用して、KMS の暗号化と復号を試してみます。
3. KMS とは
KMS( Key Management Service )とは、AWS のサービスやアプリケーションで使う暗号化・復号のための鍵を安全に生成・管理・提供するサービスです。
KMS では、マスターキーとデータキーという2種類の鍵を暗号化に使用します。

🗝️ マスターキー
データキーを暗号化する鍵です。( CMK や KMS キーとも呼ばれる)
この鍵は KMS で管理され、ユーザーが直接アクセスすることはありません。
🔑 データキー
データを暗号化する鍵です。
マスターキーの KeyID を元に作成されます。
KMS による暗号化には2つの方式があります。ひとつはマスターキーのみ使用する直接暗号化、もうひとつはマスターキーとデータキーを両方とも利用するエンベロープ暗号化です。
次の項目で、それぞれの仕組みと特徴を説明します。
3.1. 直接暗号化
直接暗号化では、以下の流れで暗号化を実施します。

① マスターキーを生成
② 平文を暗号化
直接暗号化では、暗号化処理を KMS 内で行うため、ネットワークを介して平文データを直接 KMS へ送る必要があります。
ただし、暗号化できるデータサイズに最大 4KB の制限があるため、パスワードなどの容量の小さいデータに適した暗号化方法です。
3.2. エンベロープ暗号化
エンベロープ暗号化では、以下の流れで暗号化を実施します。

① マスターキーを作成
② 作成したマスターキーでデータキーを作成
③ データキーで平文を暗号化
エンベロープ暗号化では、平文データを KMS に送信する必要がなく、ネットワークを介してやり取りするのは非常にサイズの小さいデータキーといくつかのリクエストのみです。
そのため、ネットワークレイテンシーが低く大容量のデータの暗号化にも対応できます。
4. boto3を使用したKMSの暗号化
では実際に、boto3 を使用して KMS の暗号化を実装していきます。
まずは boto3 を使用する前の下準備です。こちらの公式ドキュメントを参考に進めます。
import base64 import boto3 kms_client = boto3.client('kms')
はじめに、import で boto3 を呼び出し、boto3.client() メソッドで、指定した AWS サービスとやり取りするための API クライアントを生成します。
最初の引数には、使いたいサービスの名前を文字列で渡します。
今回はKMSを利用するので kms と指定しました。
他に boto3 で利用できるサービスについてはこちらを参照してみてください。
4.1. 直接暗号化
では、直接暗号化から図に沿って実装していきます。

① マスターキーを作成
master_key = kms_client.create_key(Description="test") key_id = master_key["KeyMetadata"]["KeyId"]
マスターキーの作成には create_key() メソッドを使用します。
(※1つのキーにつき月$1の固定費用が加算されるため、create_key()を呼んで新しいキーを生成する際は注意しましょう。)
戻り値にはマスターキーを識別するための KeyID や Arn などが含まれます。 次の【② 平文を暗号化】で KeyID を使用するので、変数に代入しておきます。
② 平文を暗号化
message = "Hello KMS!" response = kms_client.encrypt(KeyId=key_id, Plaintext=message) ciphertext = response["CiphertextBlob"] ciphertext_b64 = base64.b64encode(ciphertext).decode("utf-8") print(f"暗号文: {ciphertext_b64}")
暗号化したいメッセージを用意し、encrypt() メソッドで暗号化を行います。
encrypt() メソッドには、① で作成した暗号化に使用するマスターキーの KeyId と、平文を Plaintext として渡します。
この戻り値 CiphertextBlob が暗号文になります。
暗号文を確認してみましょう。
暗号文: AQICAHhvlCAIKBiZLbmYC5OUYizSc4jOAlrJMRdE1UaEk0OwMgEDWVNbWLfsG8MRXABKZeIYAAAAaDBmBgkqhkiG9w0BBwagWTBXAgEAMFIGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMp4mSgjCjZ6suIUJQAgEQgCWycnkcD1k370Qazl3Gjz7AoO/UfZYqhI0u1jMx6vezzMzUiwvZ
無事暗号化されたようです🎉
復号も試してみましょう。
response = kms_client.decrypt(CiphertextBlob=ciphertext) plaintext = response["Plaintext"] plaintext_str = plaintext.decode("utf-8") print(f"復号結果: {plaintext_str}")
復号には decrypt() メソッドを使用します。このとき CiphertextBlob に暗号文を渡します。
戻り値の Plaintext が復号結果です!
復号結果を確認してみましょう。
復号結果: Hello KMS!
平文と同じメッセージを確認できました🎊
4.2. エンベロープ暗号化
エンベロープ暗号化も図に沿って実装していきましょう。

① マスターキーを作成
こちらは直接暗号化と同様です。
master_key = kms_client.create_key(Description="test") key_id = master_key["KeyMetadata"]["KeyId"]
② 作成したマスターキーでデータキーを作成
response = kms_client.generate_data_key(KeyId=key_id, KeySpec="AES_256") data_key = response["Plaintext"] encrypted_data_key = response["CiphertextBlob"]
generate_data_key() メソッドを使用して、データキーを作成します。
このとき、① で作成した暗号化に使用するマスターキーの KeyId と、データキーの長さを指定する KeySpec を渡します。
KeySpec では128ビットか256ビットのどちらかを指定することができます。
戻り値の Plaintext がデータキー、CiphertextBlob が暗号化されたデータキーとなります。
後ほど利用するので変数に代入しておきます。
③ データキーで平文を暗号化
from Crypto.Cipher import AES import os message = "Hello KMS!" data = message.encode("utf-8") nonce = os.urandom(12) aes = AES.new(data_key, AES.MODE_GCM, nonce) ciphertext, tag = aes.encrypt_and_digest(data) ciphertext_b64 = base64.b64encode(ciphertext).decode("utf-8") print(f"暗号文: {ciphertext_b64}")
暗号化したいメッセージを用意し、今回は AES-GCM で暗号化します。 事前に、Python の暗号化ライブラリである PyCryptodome の AES 暗号モジュールと os モジュールを import しておきましょう。
AES.new() で暗号化オブジェクトを生成します。このとき暗号化に使用する鍵、モード、nonce を指定します。
鍵は② で作成したデータキーを使用しましょう。
nonce は暗号化処理で各セッションを一意にするために使うランダムな値です。(事前に設定しなければ自動生成されるようです。)
次に、生成したオブジェクトから encrypt_and_digest() メソッドを使用してデータの暗号化と認証タグを生成します。
このとき、データはバイト列で渡す必要があるため注意しましょう。
また、認証タグはデータの整合性を確認するために生成されるもので、nonce とともに後の復号に利用します。
暗号文を確認してみます。
暗号文: NvYMhoCnTREG/w==
無事暗号化されたようです🎉
復号も試してみましょう。
エンベロープ暗号化では次の流れで復号します。

① 暗号化済みデータキーの復号を依頼
response = kms_client.decrypt(CiphertextBlob=encrypted_data_key)
decrypted_data_key = response["Plaintext"]
decrypt() メソッドを用いて、暗号化の際に【② 作成したマスターキーでデータキーを作成】で受け取った暗号化済みのデータキーを CiphertextBlob に渡し復号します。
戻り値の Plaintext が復号されたデータキーになります。
② 復号されたデータキーで暗号文を復号
aes = AES.new(decrypted_data_key, AES.MODE_GCM, nonce) plaintext = aes.decrypt_and_verify(ciphertext, tag) plaintext_str = plaintext.decode("utf-8") print(f"復号結果: {plaintext_str}")
AES.new() で暗号化オブジェクトを生成します。このとき暗号化に使用する鍵、モード、nonce を指定します。鍵は ① で入手した復号済みのデータキーを使用しましょう。
decrypt_and_verify() で ciphertext に暗号文、暗号化時に生成された tag を渡して復号します。
復号結果を確認してみましょう。
復号結果: Hello KMS!
平文と同じメッセージを確認できました🎊
5. おわりに
今回は、KMS を使った暗号化と復号の基本を boto3 を用いて実践しました。
最初は暗号化に苦手意識がありましたが、流れをイメージして一歩ずつ実装していくことで、徐々に理解が深まりました。
同じように苦手意識を持っていた方に本記事が少しでも参考になれば幸いです。
おまけ
直接暗号化のコード全文
import base64 import boto3 kms_client = boto3.client('kms') # 暗号化 # ① マスターキーを生成 master_key = kms_client.create_key(Description="test") key_id = master_key["KeyMetadata"]["KeyId"] # ② 平文を暗号化 message = "Hello KMS!" response = kms_client.encrypt(KeyId=key_id, Plaintext=message) ciphertext = response["CiphertextBlob"] ciphertext_b64 = base64.b64encode(ciphertext).decode("utf-8") print(f"暗号文: {ciphertext_b64}") # 復号 response = kms_client.decrypt(CiphertextBlob=ciphertext) plaintext = response["Plaintext"] plaintext_str = plaintext.decode("utf-8") print(f"復号結果: {plaintext_str}")
エンベロープ暗号化のコード全文
import base64 import os from Crypto.Cipher import AES import boto3 kms_client = boto3.client('kms') # 暗号化 # ① マスターキーを生成 master_key = kms_client.create_key(Description="test") key_id = master_key["KeyMetadata"]["KeyId"] # ② 作成したマスターキーでデータキーを作成 response = kms_client.generate_data_key(KeyId=key_id, KeySpec="AES_256") data_key = response["Plaintext"] encrypted_data_key = response["CiphertextBlob"] # ③ データキーで平文を暗号化 message = "Hello KMS!" data = message.encode("utf-8") nonce = os.urandom(12) aes = AES.new(data_key, AES.MODE_GCM, nonce) ciphertext, tag = aes.encrypt_and_digest(data) ciphertext_b64 = base64.b64encode(ciphertext).decode("utf-8") print(f"暗号文: {ciphertext_b64}") # 復号 # ① 暗号化済みデータキーの復号を依頼 response = kms_client.decrypt(CiphertextBlob=encrypted_data_key) decrypted_data_key = response["Plaintext"] # ② 復号されたデータキーで暗号文を復号 aes = AES.new(decrypted_data_key, AES.MODE_GCM, nonce) plaintext = aes.decrypt_and_verify(ciphertext, tag) plaintext_str = plaintext.decode("utf-8") print(f"復号結果: {plaintext_str}")