JWT signature with AWS KMS

Adisesha Reddy G
2 min readFeb 19, 2023

This post explains how you can generate a JWT signature using keys managed by AWS KMS. If you like to see the code first, head to jwt-kms-poc. The code is a port of jwt-poc which is the code repository for JSON Web Token (JWT) Cheat Sheet for Java.

I assume you have basic knowledge of JWT. I will head to the signature part. A JWT has three parts: header, payload, and signature. It looks like header.payload.signature. The header and payload are base64url encoded. The signature verifies the authenticity and integrity of the information in the payload.

Signature Algorithm

To create the signature, you need an algorithm. Here are the recommended/required algorithms from rfc7518:

  • HMAC using SHA-256. Symmetric algorithm.
  • RSASSA-PKCS1-v1_5 using SHA-256. Asymmetric algorithm.
  • ECDSA using P-256 and SHA-256. Asymmetric algorithm.

HMAC, a symmetric algorithm, is not recommended for JWT signatures. We are going to use an asymmetric algorithm RSASSA-PKCS1-v1_5 using SHA-256 . You can modify the example to use ECDSA using P-256 and SHA-256. Check this StackExchange question on why you need to choose an asymmetric algorithm.

Keys Generation

An asymmetric algorithm uses a private key for signing and a public key for verification. Only issuers can access a private key, while the public key can be shared. You can generate these keys using a library like OpenSSL or a key management system like AWS KMS or Google KMS. There are several advantages of using a KMS. Some are:

To generate keys in AWS, follow the instructions here. Select Sign and verify as Key usage , and RSA_2048 as Key spec . Note down the Key Id.

Signature Generation

The signature is generated by signing header.payload. Check this for how to arrive at this. In the header, alg the value is RS256 . Change the alg value according to the algorithm you used. For ECDSA, using P-256 and SHA-256, it is ES256.

Once you have header.payload you can invoke Sign API using SDK in your favorite language. Here is a sample code in Kotlin,

fun sign(headerBytes: ByteArray, payloadBytes: ByteArray): String {
val contentBytes = headerBytes + '.'.code.toByte() + payloadBytes
val signRequest = SignRequest.builder()
.keyId(signVerifyKeyId)
.messageType(MessageType.RAW)
.message(SdkBytes.fromByteArray(contentBytes))
.signingAlgorithm(SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_256)
.build()
val signResponse = kmsClient.sign(signRequest)
return base64UrlEncoder.encodeToString(signResponse.signature().asByteArray())
}

Verification

Invoke Verify API with header.payload and signature . Here is a sample code,

//Verify signature with KMS
val signableContent = "${jwt.header}.${jwt.payload}".toByteArray(StandardCharsets.UTF_8)
val signature = base64UrlDecoder.decode(jwt.signature)
val kmsVerifyRequest = VerifyRequest.builder()
.keyId(signVerifyKeyId)
.message(SdkBytes.fromByteArray(signableContent))
.signature(SdkBytes.fromByteArray(signature))
.signingAlgorithm(SigningAlgorithmSpec.RSASSA_PKCS1_V1_5_SHA_256)
.build()
kmsClient.verify(kmsVerifyRequest)

--

--

Adisesha Reddy G

I write to learn. My posts are to log my technical endeavors and don’t reflect my work at a company. https://www.linkedin.com/in/adisesha