Using AWS KMS via the CLI

Off the back of local-kms, I've been getting a few questions regarding how to interact with it via the CLI. So here are a few examples of how you can use AWS KMS (or local-kms) via the CLI.

The examples here focus on demonstrating how to use AWS KMS, not as examples of how to perform 'good' encryption. Please don't use these snippets in production systems unless you know what you're doing. If you want to use KMS to manage keys for encrypting/decrypting files, I'd strongly recommend you use AWS's own tool to do so.

You can use KMS directly?

There are a couple of use cases of KMS.

AWS Managed Services

This is perhaps the most likely place most people see KMS used. AWS integrates KMS with many of its most popular services. You can use KMS with S3, DynamoDB, EBS, RDS, etc. etc.. etc...

In this context, AWS always manages all of the encryption/decryption work themselves. The use case here is that you get a CloudTrail audit log of keys accessed for AWS Managed Keys, or control over key permissions (plus the audit log), for Customer Managed Keys.

Lots has already been written on using KMS with AWS services; thus, I'm not covering it here.

Direct integration with KMS

You can also use KMS to manage symmetric keys for your own projects. Typically this would be in the form of envelope encryption, but if your plaintext is under 4 kilobytes, KMS can also perform the actual encrypt/decrypt operations.

In the examples below, I show how you can use KMS to encrypt and decrypt a short string. Then I give an example of how you could use envelope encryption to encrypt a file of any size.

Using KMS from the CLI

The main confusing aspect of accessing KMS from the cli is that the aws command automatically base64 encodes our input for us, but doesn't base64 decode the output.

Or said another way:

  • Don't base64 encode your inputs; but
  • You need to base64 decode the outputs.

To run the following examples, you'll need access to a key. If you don't have one, you can create one with:


aws kms create-key
    

This returns a KeyId. In the examples below, I use an KeyId of f17e7443-1bda-4fd7-990c-7ac84edd2247.

Encrypting data directly with KMS


aws kms encrypt \
--key-id f17e7443-1bda-4fd7-990c-7ac84edd2247 \
--plaintext "My Test String"
    

This returns the KeyId used, plus a CiphertextBlob:


{
    "KeyId": "arn:aws:kms:eu-west-2:111122223333:key/f17e7443-1bda-4fd7-990c-7ac84edd2247"
    "CiphertextBlob": "AQICAHhL5fG/So/TVO0H5DDhNMvAsaFiXzcB41glSsu4qCgmIwH7G0fVrUTbSNA9fUc31anYAAAAbDBqBgkqhkiG9w0BBwagXTBbAgEAMFYGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMTPBgFawm+XnPXm6eAgEQgCnfKLLGUfQ6iMIzBRxT6RG/b+3Mvy69ACbCbZtZi8ywkbLS/Ob5pP5Itg==",
}
    

The CiphertextBlob is your encrypted data, plus additional metadata used to aid decryption later on.

Note that CiphertextBlob is base64 encoded. When it comes to decrypting that CiphertextBlob, you'll need to pass the raw (non-encoded) binary to the decrypt command.

You'd typically, therefore, write the output of encrypt to a file. AWS gives us an example of how to do this, including decoding the returned base64, along these lines:


aws kms encrypt --key-id f17e7443-1bda-4fd7-990c-7ac84edd2247 \
--plaintext "My Test String" \
--output text \
--query CiphertextBlob | base64 --decode > output.dat
    

Decrypting data directly with KMS


aws kms decrypt --ciphertext-blob fileb://output.dat
    

Note that the file we're referring to is the raw binary, not base64 encoded. And because we don't want to apply any system-level encoding to this file either, we specify it used the fileb:// prefix.

Also note we don't need to pass any details as to which key to use for decryption, as this is contained within the metadata of the CiphertextBlob.

The response from decrypt looks something like


{
    "KeyId": "arn:aws:kms:eu-west-2:111122223333:key/f17e7443-1bda-4fd7-990c-7ac84edd2247",
    "Plaintext": "TXkgVGVzdCBTdHJpbmc="
}
    

Similarly to the encrypt opteration, that actual data returned - Plaintext - is base64 encoded.

We can then do


echo -n "TXkgVGVzdCBTdHJpbmc=" | base64 --decode
    

To get our original unencrypted input, My Test String.

Or to combine that all together, AWS gives us the following example:


aws kms decrypt --ciphertext-blob fileb://output.dat \
--output text \
--query Plaintext | base64 --decode
    

How to use KMS for envelope/hybrid encryption

Envelope encryption is where we use KMS to generate a data key for us, which we can then use to encrypt our data. KMS also gives us a ciphertext version of the data key, which we can safely store alongside our own encrypted data. When the time comes to decrypt it, we pass the data key's ciphertext back to KMS, which returns the plain text key, which we can then use to decrypt our data.

First, we need to generate a new data key


aws kms generate-data-key \
--key-id f17e7443-1bda-4fd7-990c-7ac84edd2247 \
--key-spec AES_128
    

Which gives us


{
    "Plaintext": "w2yWROlgETF+e4naEK4cvg==",
    "KeyId": "arn:aws:kms:eu-west-2:111122223333:key/f17e7443-1bda-4fd7-990c-7ac84edd2247",
    "CiphertextBlob": "AQIDAHjqvZmnrnhNwr884nQeAbxlmDFsyjYr2GwCr+VzhaqvuwHSWi+LYbYPzSdKrImLSlLmAAAAbjBsBgkqhkiG9w0BBwagXzBdAgEAMFgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM9f5J5zZ86ojaUI0BAgEQgCuV32/07DM8o8+kF1E/kESaEMRrunjHsHF+7xz8eRr+d0UjSsXGg3mx/7GX"
}
    

We are given a Plaintext version of the key, which we should use to encrypt our data, then immediately and securely discard. We also get a Ciphertext version of the key, which we can safely store alongside the encrypted file, for when we need to decrypt it at a later date.

So let us assume we have a file, secrets.txt.

We can encrypt this file using the returned details using OpenSSL (for example). OpenSSL takes a hex key, so we convert our base64 encoded Plaintext to hex


base64 --decode <<< 'w2yWROlgETF+e4naEK4cvg==' | xxd -p
    

Gives c36c9644e96011317e7b89da10ae1cbe.

We can then pass that key, along with our file name, to openssl:


openssl enc -aes-128-cbc \
-K c36c9644e96011317e7b89da10ae1cbe \
-iv 0 -in secrets.txt -out secrets.txt.enc
    

We've now used our data key to encrypt secrets.txt, with the resulting ciphertext bring stored in secrets.txt.enc.

We also need to keep a copy of the CiphertextBlob, so let's pop that in a file aswell


base64 --decode <<< 'AQIDAHjqvZmnrnhNwr884nQeAbxlmDFsyjYr2GwCr+VzhaqvuwHSWi+LYbYPzSdKrImLSlLmAAAAbjBsBgkqhkiG9w0BBwagXzBdAgEAMFgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQM9f5J5zZ86ojaUI0BAgEQgCuV32/07DM8o8+kF1E/kESaEMRrunjHsHF+7xz8eRr+d0UjSsXGg3mx/7GX' > secrets.txt.key
    

Now when we come to wanting to decrypt the file, first, we must return our original plaintext key by decrypting secrets.txt.key.


aws kms decrypt --ciphertext-blob fileb://secrets.txt.key
    

Which returns


{
    "KeyId": "arn:aws:kms:eu-west-2:111122223333:key/f17e7443-1bda-4fd7-990c-7ac84edd2247",
    "Plaintext": "w2yWROlgETF+e4naEK4cvg=="
}
    

We should regonise that Plaintext as the same as when we originally generated the key. We re-encode it again from base64 to hex.


base64 --decode <<< 'w2yWROlgETF+e4naEK4cvg==' | xxd -p
c36c9644e96011317e7b89da10ae1cbe
    

And finally we can decrypt the file


openssl enc -aes-128-cbc -d \
-K c36c9644e96011317e7b89da10ae1cbe \
-iv 0 -in secrets.txt.enc -out decrypted-secrets.txt
    

Resulting in secrets.txt.enc being decrypted into decrypted-secrets.txt.

Local KMS

Local KMS is a mock version of AWS' Key Management Service, for local development and testing. Or just for if you want to have a play around with KMS, without having to pay for a key in AWS.