試運転ブログ

技術的なあれこれ

AWS KMSをさわってみた

AESなどの対称鍵暗号でデータを暗号化すると、データを読み取ることができる対象を鍵を知ってるものだけにすることができる。 そのため、何かのデータを暗号化したとき、暗号化に使用した鍵がどのように管理されているかはとても重要となります。

鍵管理は、ファイルのパーミッションで読めるuserを制限したりすることが多いですが、Amazonの提供するAWS KMSというサービスを用いて鍵管理(データキーを生成、暗号化、復号)を行ってみます。

主に以下のページを参考にしました。サンプルコードでは、エンベロープ暗号化と呼ばれているものを試しています。

docs.aws.amazon.com

AWSAPI呼び出しはpythonのbotoというライブラリを使った。

github.com

KMSでキーの作成と、アカウントに権限付与などは以下のリンクを参考にした。

dev.classmethod.jp

キー生成と暗号化

AES周りは今回はあまり重要じゃないので、ブロック長ぴったし(16バイト)の文字列を1ブロック暗号化する。鍵にはAWS KMSで生成した値を使用する。

#!/usr/bin/env python
import base64
from boto import kms
from Crypto.Cipher import AES

conn = kms.connect_to_region('us-east-1')
arn = 'arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222'

data_key = conn.generate_data_key(arn, key_spec='AES_128')
print(data_key)

# 16 bytes
plaintext = b'Hello, World!!!!'

encrypter = AES.new(data_key['Plaintext'])
encrypted_data = base64.b64encode(encrypter.encrypt(plaintext))

print(encrypted_data)

※ botoは、 ~/.boto からAPIの認証情報を読み込んでる。

出力

{'CiphertextBlob': b"\x01\x02\x03\x00x6#\xcde\xbb'2d\x9d\xb1?\xf3<\x1a\xddI\xf9Zo\xb65\xd9\xa8\x1b\xd97\x03Q\x87\xf4\xd7\xd1\x01\x88\x93P\xb6\xaf\xd1YYuP\x13]\n\x17F \x00\x00\x00n0l\x06\t*\x86H\x86\xf7\r\x01\x07\x06\xa0_0]\x02\x01\x000X\x06\t*\x86H\x86\xf7\r\x01\x07\x010\x1e\x06\t`\x86H\x01e\x03\x04\x01.0\x11\x04\x0c\xb1y\xbbj\xb4i;}\xd3dw\xf9\x02\x01\x10\x80+<\x1e!o\x17\x12\x84\xfdh\xfe\x89\x05w\xc2\xa5\xado\x0c\x9aF9D\x8a\xeb\x1d\x19\xe5\xaf\xeeP\xb5\xb2\xd5H\xef4\x8f\xcd\x10\xaf\xf2\xf1\xe0", 'KeyId': 'arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222', 'Plaintext': b'\xc3\x06\xe9\x1f\xe1\xb7d\xd6sn\x97\x18>yR\x07'}

b'2kvkQDMonVYyv3TAwsMbiw=='

data_keyは整形すると、

{
    "CiphertextBlob": "\x01\x02\x03\x00x6#\xcde\xbb'2d\x9d\xb1?\xf3<\x1a\xddI\xf9Zo\xb65\xd9\xa8\x1b\xd97\x03Q\x87\xf4\xd7\xd1\x01\x88\x93P\xb6\xaf\xd1YYuP\x13]\n\x17F \x00\x00\x00n0l\x06\t*\x86H\x86\xf7\r\x01\x07\x06\xa0_0]\x02\x01\x000X\x06\t*\x86H\x86\xf7\r\x01\x07\x010\x1e\x06\t`\x86H\x01e\x03\x04\x01.0\x11\x04\x0c\xb1y\xbbj\xb4i;}\xd3dw\xf9\x02\x01\x10\x80+<\x1e!o\x17\x12\x84\xfdh\xfe\x89\x05w\xc2\xa5\xado\x0c\x9aF9D\x8a\xeb\x1d\x19\xe5\xaf\xeeP\xb5\xb2\xd5H\xef4\x8f\xcd\x10\xaf\xf2\xf1\xe0",
    "KeyId": "arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222",
    "Plaintext": "\xc3\x06\xe9\x1f\xe1\xb7d\xd6sn\x97\x18>yR\x07"
}

AES の鍵長 128bitで鍵を生成すると、CiphertextBlogとKeyIdとPlainTextが返ってきます。

暗号化の鍵にはPlaintextの値を使用します。鍵長が128bitのオプションで生成しているので16バイト(=128bit)なことも確認できます。Plaintextは、暗号化に使用後に保持しません(むしろ保持したらKMS使う意味がない)。 CiphertextBlobは、PlaintextをAWS KMSのマスターキー(KeyIdに紐づく鍵)で暗号化した値となっていて、この値を保持しておきます。暗号文を復号するときに鍵は、CiphertextBlobをマスターキーで復号して鍵を取り出します。

KMSの説明されているサイトで使用されている画像との対応関係は、

f:id:otameshi61:20180108191930p:plain

https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/concepts.html

  • Plaintext data: Hello, World!!!!
  • Encrypted data: 2kvkQDMonVYyv3TAwsMbiw==
  • Data Key: Plaintext
  • Encrypted data key: CiphertextBlob

となっています。

CiphertextBlobから鍵を取り出すには、このKeyIdにアクセスを許可されているAWSAPIキーでしか読み出せません。APIキーの作成などは最初の方に書いたブログを参照してください。キーの作成や無効化などはAWSコンソールでぽちぽちしてたら行えます。

マスターキーに対しても、無効化させておくことなども可能です。有事の際や、普段は絶対に使わない情報などは暗号化後に鍵を無効化しておくなどの使い方があるかと思います。

AWSでCloudTrailを有効にしていると、APIの呼び出しで以下のようなログを取ることができます(適当に一部を切り出しています)。記録されるAPI一覧をぱっとみましたが、だいたい重要なもの(たぶん全部...?)はカバーされているようにみえました。 なので、鍵の使用形跡などをログから読み取ることができます。

{
  "eventVersion": "1.05",
  "userIdentity": {
    "type": "IAMUser",
    "userName": "saso"
  },
  "eventTime": "2018-01-08T09:32:33Z",
  "eventSource": "kms.amazonaws.com",
  "eventName": "GenerateDataKey",
  "awsRegion": "us-east-1",
  "userAgent": "Boto/2.48.0 Python/3.6.1 Darwin/16.7.0",
  "requestParameters": {
    "keySpec": "AES_128",
  },
  "responseElements": null,
  "readOnly": true,
  "eventType": "AwsApiCall",
  "recipientAccountId": "071542545104"
}

AWS KMSを使って鍵を管理する強みは、鍵自体の管理だけでなく、他のAWSの機能と同等にAPIキーの管理、ログ収集もできるあたりかなと思います。

復号

復号できることも確認します。

#!/usr/bin/env python
import base64
from boto import kms
from Crypto.Cipher import AES

conn = kms.connect_to_region('us-east-1')
arn = 'arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222'

ciphertext_blob = b"\x01\x02\x03\x00x6#\xcde\xbb'2d\x9d\xb1?\xf3<\x1a\xddI\xf9Zo\xb65\xd9\xa8\x1b\xd97\x03Q\x87\xf4\xd7\xd1\x01\x88\x93P\xb6\xaf\xd1YYuP\x13]\n\x17F \x00\x00\x00n0l\x06\t*\x86H\x86\xf7\r\x01\x07\x06\xa0_0]\x02\x01\x000X\x06\t*\x86H\x86\xf7\r\x01\x07\x010\x1e\x06\t`\x86H\x01e\x03\x04\x01.0\x11\x04\x0c\xb1y\xbbj\xb4i;}\xd3dw\xf9\x02\x01\x10\x80+<\x1e!o\x17\x12\x84\xfdh\xfe\x89\x05w\xc2\xa5\xado\x0c\x9aF9D\x8a\xeb\x1d\x19\xe5\xaf\xeeP\xb5\xb2\xd5H\xef4\x8f\xcd\x10\xaf\xf2\xf1\xe0"

data_key = conn.decrypt(ciphertext_blob)
print(data_key)

# 16 bytes
encrypted = base64.b64decode(b'2kvkQDMonVYyv3TAwsMbiw==')

decrypter = AES.new(data_key['Plaintext'])
decrypted = decrypter.decrypt(encrypted)

print(decrypted)

出力

{'KeyId': 'arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222', 'Plaintext': b'\xc3\x06\xe9\x1f\xe1\xb7d\xd6sn\x97\x18>yR\x07'}
b'Hello, World!!!!'

data_keyは以下の値になっている。

{
    "KeyId": "arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222",
    "Plaintext": "\xc3\x06\xe9\x1f\xe1\xb7d\xd6sn\x97\x18>yR\x07"
}

また、このKeyIdをdisableにすると以下のようなエラーが返ってくる。

boto.kms.exceptions.DisabledException: DisabledException: 400 Bad Request
{'__type': 'DisabledException', 'message': 'arn:aws:kms:us-east-1:012345678910:key/00000000-aaaa-1111-bbbb-222222222222 is disabled.'}

まとめ

AWS KMSを使ってエンベロープ暗号化と呼ばれている暗号化・復号を試してみた。鍵を管理するぞ!、という気持ちになったときに直感的に必要な機能が用意されている印象だった。 鍵へのアクセスが、ちゃんとログとして出力されるのは、個人的には一番良いなと感じた。

参考リンク

他にも以下のようなリンクを参考にしました。

docs.aws.amazon.com

Envelope Encryption using AWS KMS, Python Boto, and PyCrypto. · GitHub