An example of how you can use HMAC keys from AWS KMS to sign JWTs.
# Create an HMAC key in AWS KMS
# Begin by creating an HMAC key in AWS KMS. You can use the AWS KMS console or call the CreateKey API action. The following example shows creation of a 256-bit HMAC key:
import time
import json
import base64
import boto3
kms = boto3.client('kms')
# Use CreateKey API to create a 256-bit key for HMAC
key_id = kms.create_key(
KeySpec='HMAC_256',
KeyUsage='GENERATE_VERIFY_MAC'
)['KeyMetadata']['KeyId']
# ---------------------------------------------------------------------------------------
# How to protect HMACs inside AWS KMS > Create an HMAC key in AWS KMS
# https://aws.amazon.com/blogs/security/how-to-protect-hmacs-inside-aws-kms/
def base64_url_encode(data):
return base64.b64encode(data, b'-_').rstrip(b'=')
# Payload contains simple claim and an issuance timestamp
payload = json.dumps({
"does_kms_support_hmac": "yes",
"iat": int(time.time())
}).encode("utf8")
# Header describes the algorithm and AWS KMS key ID to be used for signing
header = json.dumps({
"typ": "JWT",
"alg": "HS256",
"kid": key_id # This key_id is from the "Create an HMAC key in AWS KMS" #example. The "Verify the signed JWT" example will later #assert that the input header has the same value of the #key_id
}).encode("utf8")
# Message to sign is of form <header_b64>.<payload_b64>
message = base64_url_encode(header) + b'.' + base64_url_encode(payload)
# Generate MAC using GenerateMac API of AWS KMS
mac = kms.generate_mac(
KeyId=key_id, # This key_id is from the "Create an HMAC key in AWS KMS"
# example
MacAlgorithm='HMAC_SHA_256',
Message=message
)['Mac']
# Form JWT token of form <header_b64>.<payload_b64>.<mac_b64>
jwt_token = message + b'.' + base64_url_encode(mac)
# ---------------------------------------------------------------------------------------
# Verify the signed JWT
def base64_url_decode(data):
return base64.b64decode(data + b'=' * (4 - len(data) % 4), b'-_')
# Parse out encoded header, payload, and MAC from the token
message, mac_b64 = jwt_token.rsplit(b'.', 1)
header_b64, payload_b64 = message.rsplit(b'.', 1)
# Decode header and verify its contents match expectations
header_map = json.loads(base64_url_decode(header_b64).decode("utf8"))
assert header_map == {
"typ": "JWT",
"alg": "HS256",
"kid": key_id # This key_id is from the "Create an HMAC key in AWS KMS"
# example
}
# Verify the MAC using VerifyMac API of AWS KMS. # If the verification fails, this will throw an error.
kms.verify_mac(
KeyId=key_id, # This key_id is from the "Create an HMAC key in AWS KMS"
# example
MacAlgorithm='HMAC_SHA_256',
Message=message,
Mac=base64_url_decode(mac_b64)
)
# Decode payload for use application-specific validation/processing
payload_map = json.loads(base64_url_decode(payload_b64).decode("utf8"))
# ---------------------------------------------------------------------------------------
# Create separate roles to control who has access to generate HMACs and who has access to validate HMACs
# {
# "Id": "example-jwt-policy",
# "Version": "2012-10-17",
# "Statement": [
# {
# "Sid": "Allow use of the key for creating JWTs",
# "Effect": "Allow",
# "Principal": {
# "AWS": "arn:aws:iam::111122223333:role/JwtProducer"
# },
# "Action": [
# "kms:GenerateMac"
# ],
# "Resource": "*"
# },
# {
# "Sid": "Allow use of the key for validating JWTs",
# "Effect": "Allow",
# "Principal": {
# "AWS": "arn:aws:iam::111122223333:role/JwtConsumer"
# },
# "Action": [
# "kms:VerifyMac"
# ],
# "Resource": "*"
# }
# ]
# }