KMSはAWSの他のサービスを使うときになんとなく設定する程度で、KMSそのものの動きはいままで理解できていませんでした。そこで、awscliで手動で暗号化・復号化の処理をしてみることで、理解を進めました。
キー作成
aws kms create-key コマンドでキーを作成します。
$ aws kms create-key
{
"KeyMetadata": {
"AWSAccountId": "123456789012",
"KeyId": "2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"Arn": "arn:aws:kms:ap-northeast-1:123456789012:key/2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"CreationDate": 1620370256.542,
"Enabled": true,
"Description": "",
"KeyUsage": "ENCRYPT_DECRYPT",
"KeyState": "Enabled",
"Origin": "AWS_KMS",
"KeyManager": "CUSTOMER",
"CustomerMasterKeySpec": "SYMMETRIC_DEFAULT",
"EncryptionAlgorithms": [
"SYMMETRIC_DEFAULT"
]
}
}
ここで作成したキーは、存在は見えるものの、キーそのもの(CMK、Customer Master Key)を見ることはできません。
AWSアカウントにあるキー一覧を確認
aws kms list-keys コマンドでキーの一覧を確認できます。さきほど作成したキーはこの中に含まれています。
$ aws kms list-keys
{
"Keys": [
{
"KeyId": "2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"KeyArn": "arn:aws:kms:ap-northeast-1:123456789012:key/2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
{
"KeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"KeyArn": "arn:aws:kms:ap-northeast-1:123456789012:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
{
"KeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"KeyArn": "arn:aws:kms:ap-northeast-1:123456789012:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
...
]
}
マネジメントコンソールで見ると、作成したキーはCustomer managed keysに入っていました。
さきほど作成した以外のキーはAWS managed keysに入っていました。
エイリアスは aws kms list-aliases コマンドなどで確認できます。
$ aws kms list-aliases
{
"Aliases": [
{
"AliasName": "alias/aws/backup",
"AliasArn": "arn:aws:kms:ap-northeast-1:123456789012:alias/aws/backup",
"TargetKeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"CreationDate": 1594564129.245,
"LastUpdatedDate": 1594564129.245
},
{
"AliasName": "alias/aws/dms",
"AliasArn": "arn:aws:kms:ap-northeast-1:123456789012:alias/aws/dms",
"TargetKeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"CreationDate": 1598578567.162,
"LastUpdatedDate": 1598578567.162
},
{
"AliasName": "alias/aws/dynamodb",
"AliasArn": "arn:aws:kms:ap-northeast-1:123456789012:alias/aws/dynamodb",
"TargetKeyId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"CreationDate": 1604415194.916,
"LastUpdatedDate": 1604415194.916
},
{
"AliasName": "alias/aws/ebs",
"AliasArn": "arn:aws:kms:ap-northeast-1:123456789012:alias/aws/ebs"
},
...
CDK作成
aws kms generate-data-key コマンドによりCDKを作成します。CDKはデータを暗号化・復号化するためのキーです。
$ aws kms generate-data-key --key-id 2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --key-spec AES_256
{
"CiphertextBlob": "AQID...",
"Plaintext": "zNn6...",
"KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
レスポンスにある、Plaintextはキーそのもので、CiphertextBlobはそのキーを最初に作成したキー(CMK、Customer Master Key)で暗号化したものです。どちらもBASE64でエンコードされた長い文字列です。このあとで試しますが、aws kms decryptコマンドで、CiphertextBlobからPlaintextを復元できます。
CDKは aws kms generate-data-key コマンドを実行するごとに異なるキーが返されます。
データを暗号化
Hello, World! というテキストを暗号化してみます。データの暗号化・復号化は、awscliではなく、適当なツールを使えばよいようです。opensslを使いました。パスフレーズにさきほど作成したCDKのPlaintextを指定します。BASE64でデコードが必要です。
$ echo 'Hello, World!' | openssl enc -e -aes256 -pbkdf2 -pass file:<(echo 'zNn6...' | base64 -d) | base64
U2FsdGVkX19Qvh8L0t+zpptfNhgHX4a/RzpNB2nql9g=
openssl enc -eコマンドの -pass には、パスフレーズの書かれたファイルを file:FILEPATH のようにファイル名で指定します。ここではファイル作成の手間を惜しんで、コマンドの中に <(...) で埋め込んでいます。<(...) の中身はPlaintextをBASE64でデコードした結果です。
openssl encコマンドの出力は暗号化された結果です。バイナリなので、コンソール上に表示できるようにBASE64でエンコードしています。
暗号化したデータを保存
データベースなどにデータを暗号化して保存する際は U2FsdGVkX19Qvh8L0t+zpptfNhgHX4a/RzpNB2nql9g= という暗号化したデータとともに、CDKのCiphertextBlobの AQID.... という文字列(またはそれをBASE64でデコードしたバイナリ)を保存しておきます。CiphertextBlobのから直接データを復号化することはできません。CDKのPlaintextが必要です。Plaintextを得るには、KMSにCiphertextBlobからの復元をリクエストする必要があります。
このあとは、暗号化したデータとCiphertextBlobが保存されていることを想定して、そこからKMSを使って復号化する手順を踏みます。
CDKのPlaintextを復元
aws kms decrypt コマンドによりCDKのCiphertextBlobからPlaintextを復元します。
$ aws kms decrypt --ciphertext-blob fileb://<(echo 'AQID...' | base64 -d)
{
"KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/2f94xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"Plaintext": "zNn6...",
"EncryptionAlgorithm": "SYMMETRIC_DEFAULT"
}
--ciphertext-blob オプションには fileb://FILEPATH のように、CiphertextBlobの AQID.... という文字列をBASE64でデコードしたバイナリを保存したファイル名を指定します。ここではファイル作成の手間を惜しんで、コマンドの中に <(...) で埋め込んでいます。<(...) の中身はCiphertextBlobをBASE64でデコードした結果です。
レスポンスされたPlaintextが、暗号化したときのPlaintextと一致しています。暗号化したときのパスフレーズを保存しておかなくても、KMSとCiphertextBlobから復元できることがわかります。
KMSはPlaintextを復元する際にはCMKを識別するためのKeyIdが必要なはずですが、 aws kms decrypt コマンドではKeyIdの指定が不要です。CiphertextBlobにCMKを識別するための情報が埋め込まれているらしいです。
データを復号化
openssl enc -d で復号化します。無事 Hello, World! が復元できました。
$ echo 'U2FsdGVkX19Qvh8L0t+zpptfNhgHX4a/RzpNB2nql9g=' | base64 -d | openssl enc -d -aes256 -pbkdf2 -pass file:<(echo 'zNn6...' | base64 -d)
Hello, World!
暗号化したデータは openssl enc -dコマンドの標準入力に渡しています。 U2FsdGVk... はBASE64でエンコードされた文字列ですので、標準入力前にBASE64でデコードしています。
openssl enc -dコマンドの -pass には、暗号化したときと同様にコマンドの中に <(...) で埋め込んでいます。

