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.
AWS KMS supports message signing and verification operations using an Elliptic Curve key. You can pick between using National Institute of Standards and Technology (NIST) curves with a key size of 256, 384 or 521 bits. Or the secp256k1 curve, commonly known for its use in Bitcoin.
AWS KMS does not currently support encryption / decryption operations with Elliptic Curve keys.
aws kms create-key \
--key-usage SIGN_VERIFY \
--customer-master-key-spec ECC_NIST_P256
Note that whilst --key-usage SIGN_VERIFY
is the only valid option, it must still be included.
This returns the key details:
{
"KeyMetadata": {
"AWSAccountId": "111122223333",
"KeyId": "024f6b2c-365a-4d86-af2e-9cc5c468afba",
"Arn": "arn:aws:kms:eu-west-2:111122223333:key/024f6b2c-365a-4d86-af2e-9cc5c468afba",
"CreationDate": 1590507285,
"Enabled": true,
"KeyUsage": "SIGN_VERIFY",
"KeyState": "Enabled",
"Origin": "AWS_KMS",
"KeyManager": "CUSTOMER",
"CustomerMasterKeySpec": "ECC_NIST_P256",
"SigningAlgorithms": [
"ECDSA_SHA_256"
]
}
}
We can see that KMS has assigned one, and only one, Signing Algorithm. This algorithm is the only valid option when using the key to sign a message. Each ECC Key Spec maps to the following signing algorithm:
ECC_NIST_P256
--> ECDSA_SHA_256
ECC_NIST_P384
--> ECDSA_SHA_384
ECC_NIST_P521
--> ECDSA_SHA_512
ECC_SECG_P256K1
--> ECDSA_SHA_256
In all cases, when KMS is signing a message, it is in fact always signing the digest of that message, generated via a SHA hash function.
KMS supports two options for generating the digest of a message - you can generate it yourself in advance or, if your message is less than or equal to 4096 bytes, you can have KMS generate the digest for you.
To have KMS sign the message Hello
, you can call:
aws kms sign \
--key-id 024f6b2c-365a-4d86-af2e-9cc5c468afba \
--signing-algorithm ECDSA_SHA_256 \
--message Hello
The message here is passed in plain text. i.e. not base64 encoded, as the AWS CLI will encode it for us.
Note that I've used the key generated earlier on this page, thus the key spec is ECC_NIST_P256
,
so I've therefore used the ECDSA_SHA_256
signing algorithm. KMS will automatically hash our message using SHA-256 for us.
This returns
{
"KeyId": "arn:aws:kms:eu-west-2:111122223333:key/024f6b2c-365a-4d86-af2e-9cc5c468afba",
"Signature": "MEUCIQDQ1CFlwej64aSAcPtgPBDuVS+HlFSxRfs4CP1O7AhxtgIgX7NHGVyy3hTKP1+jKlw98vQinb7nJ3tAGs8GnldVRu0=",
"SigningAlgorithm": "ECDSA_SHA_256"
}
The message's signature is returned, along with the Key ARN and the Signing Algorithm. The Signature is always returned as a base64 encoded string. i.e. the AWS CLI does not base64 decode it for us.
It's important to note that you'll need to pass the KeyId and SigningAlgorithm when Verifying the message with KMS, so keep a note of these too.
You can save the signature directly to a file using:
aws kms sign \
--key-id 024f6b2c-365a-4d86-af2e-9cc5c468afba \
--signing-algorithm ECDSA_SHA_256 \
--message Hello \
--output text --query Signature | base64 --decode > signature-raw.sign
The reason we base64 decode the message here is that when passing it back to KMS for verification, the AWS CLI expects it unencoded.
We need to perform the extra hashing step ourselves in this context. This has two advantages:
When we hash the message ourselves, we must use the correct hashing algorithm. For example, as our key spec is ECC_NIST_P256
, our
signing algorithm in KMS is therefore ECDSA_SHA_256
, so we must hash our message using SHA-256.
So for example, we can use OpenSSL to hash our message with:
echo -n Hello | openssl dgst -sha256
Which gives us:
185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969
The AWS CLI actually wants the digest in unencoded binary however, so we can do:
echo -n Hello | openssl dgst -sha256 -binary > message.sha256
We can then sign the message similarly to how we did before:
aws kms sign \
--key-id 024f6b2c-365a-4d86-af2e-9cc5c468afba \
--signing-algorithm ECDSA_SHA_256 \
--message-type DIGEST \
--message fileb://message.sha256
In this instance we must specify that the message type as DIGEST
,
and we pass the binary digest in from the pre-generated file as the message.
This gives us:
{
"KeyId": "arn:aws:kms:eu-west-2:111122223333:key/024f6b2c-365a-4d86-af2e-9cc5c468afba",
"Signature": "MEYCIQCBQfufXvIvz0Nis9pdE+8IWI1rEwYwG68KMSl6yLJYlgIhAKipwa6Yy9kuHpCr10ugh1nAhPyRTnrcYtVd7QvFblhV",
"SigningAlgorithm": "ECDSA_SHA_256"
}
As before we can generate the signature and save it directly to a file:
aws kms sign \
--key-id 024f6b2c-365a-4d86-af2e-9cc5c468afba \
--signing-algorithm ECDSA_SHA_256 \
--message-type DIGEST \
--message fileb://message.sha256 \
--output text --query Signature | base64 --decode > signature-digest.sign
To verify a message we must pass the same KeyId, Signing Algorithm and Message passed when we signed the message, plus the signature that was returned:
aws kms verify \
--key-id 024f6b2c-365a-4d86-af2e-9cc5c468afba \
--signing-algorithm ECDSA_SHA_256 \
--message Hello \
--signature fileb://signature-raw.sign
KMS will then verify that the signature passed is valid for the given combination of Key and Message:
{
"KeyId": "arn:aws:kms:eu-west-2:111122223333:key/024f6b2c-365a-4d86-af2e-9cc5c468afba",
"SignatureValid": true,
"SigningAlgorithm": "ECDSA_SHA_256"
}
If the verification fails for any reason, KMS throws a KMSInvalidSignatureException
.
This works exactly as the RAW message type, other than we set --message-type DIGEST
, and we pass in the binary message digest.
aws kms verify \
--key-id 024f6b2c-365a-4d86-af2e-9cc5c468afba \
--signing-algorithm ECDSA_SHA_256 \
--message-type DIGEST \
--message fileb://message.sha256 \
--signature fileb://signature-digest.sign
{
"KeyId": "arn:aws:kms:eu-west-2:111122223333:key/024f6b2c-365a-4d86-af2e-9cc5c468afba",
"SignatureValid": true,
"SigningAlgorithm": "ECDSA_SHA_256"
}
Whilst there is no way to export the Private Key from KMS, you can export the Public Key and use it to verify messages locally that have been signed by KMS.
To export the public key we use:
aws kms get-public-key \
--key-id 024f6b2c-365a-4d86-af2e-9cc5c468afba
Which gives us:
{
"KeyId": "arn:aws:kms:eu-west-2:111122223333:key/024f6b2c-365a-4d86-af2e-9cc5c468afba",
"PublicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuD3O/vISzg4EpFHAFe1jJudnP31YL7MGbxcVwRMnylnA/gw7ty2umsdzluFxwqOGO7xr63pozNjOfSzmJUoDuQ==",
"CustomerMasterKeySpec": "ECC_NIST_P256",
"KeyUsage": "SIGN_VERIFY",
"SigningAlgorithms": [
"ECDSA_SHA_256"
]
}
PublicKey
here is a DER-encoded X.509 public key, base64 encoded.
We can save this to a file using:
aws kms get-public-key \
--key-id 024f6b2c-365a-4d86-af2e-9cc5c468afba \
--output text --query PublicKey | base64 --decode > key.der
Many applications for verifying signatures support DER encoded keys, however if needed you can convert it to a PEM encoding using:
openssl ec -inform DER -in key.der -pubin > key.pem
If you used KMS to generate the message's digest originally, you will now need to generate a version yourself. OpenSSL's dgst
function directly
supports generating a digest and verifying it in a single command:
echo -n Hello | openssl dgst -sha256 -keyform DER -verify key.der -signature signature-raw.sign
If you generated the digest yourself originally, and still have it, you can use OpenSSL's pkeyutl
function:
openssl pkeyutl -verify -pubin -keyform DER -inkey key.der -in message.sha256 -sigfile signature-digest.sign