4

I have used AWS KMS to create a signature. Now I need to verify the signature outside AWS using java security APIs. Although iam able to get a public key successfully, the verify method returns false everytime. I used bouncycastleprovider but was not successful. What are the other providers i can use to verify ?

1 Answers1

4

I'll start from the top, because how you make the Sign request to generate the signature effects how you verify it.

So we have a message:

byte[] rawMessage = "This is my message".getBytes(StandardCharsets.UTF_8);

And we need to send it to KMS to be encrypted.

The first thing we need to know is what algorithms we'll be using to generate the signature. Signatures need both an encryption algorithm as well as a digest algorithm.

Here is the document on KMS's available algorithms, and here is the corresponding documentation for Java 8. It looks like RSA with SHA is what both support, so let's go with RSA and SHA-512. That means that our Sign request should specify RSASSA_PKCS1_V1_5_SHA_512 as the signing algorithm, and in Java we'll pass SHA512withRSA to Signature.getInstance.

The next thing we need to know is whether or not the data we'll be signing can ever exceed 4kb. If it can, we need to pass in MESSAGE_DIGEST as the MessageType parameter of the sign request and we'll also need to send a digest on the Message parameter instead of the raw message.

Let's say our message might exceed 4k. Because SHA-512 is our digest algorithm, we'll need to get an SHA-512 digest of our data:

MessageDigest digester = MessageDigest.getInstance("SHA-512");

byte[] messageDigest = digester.digest(rawMessage);

Now we can make our Sign call and get a signature:

SignRequest signReq =
    new SignRequest()
        .withKeyId("ARN or alias of key")
        .withMessageType(MessageType.DIGEST)
        .withMessage(ByteBuffer.wrap(messageDigest))
        .withSigningAlgorithm(RSASSA_PKCS1_V1_5_SHA_512);

SignResult signResult = kms.sign(signReq);

byte[] signatureBytes = signResult.getSignature().array();

We need the public key from AWS, so either download the .pem file or copy & paste the key from the AWS console. Either way, all we care about is the base64-encoded part between the first and last lines, which we'll use to create a PublicKey object in Java's crypto system:

String base64EncodedKey = "...";

byte[] pubKeyBytes = Base64.getMimeDecoder().decode(base64EncodedKey);

X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pubKeyBytes);

PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(pubKeySpec);

Finally, we get to verify the signature. Note that you'll always pass in the entire message here, even if you only sent AWS a digest:

Signature sig = Signature.getInstance("SHA512withRSA");

sig.initVerify(publicKey);

sig.update(rawMessage);

boolean signatureValid = sig.verify(signatureBytes);

I hope that helps! I don't know nearly as much about cryptography as I should and may have messed up some terminology.

gabeg
  • 149
  • 2
  • Thank you. It was really useful. The only point I would add is when you already have both kms insatnce and the masterKeyId, you can retrieve your public key from there as well and store it into a map: `GetPublicKeyRequest getKeyRequest = new GetPublicKeyRequest(); getKeyRequest.setKeyId(masterKeyId); awsKms.getPublicKey(getKeyRequest);` – user3504158 Aug 26 '20 at 13:20
  • Could not get the verification to work with PSS types of algos(eg RSASSA_PSS_SHA_256). Used the VerifyRequest class to have it verified inside AWS, which works fine. – Saud Ali Sep 24 '20 at 10:51
  • Same thing for RSASSA_PSS_SHA_256, but I'm stuck as I don't have access to AWSs external verification. – delki8 Feb 16 '23 at 16:43