Decrypt AWS RDS Aurora Activty Stream in Javascript

0

The AWS RDS Aurora mysql cluster activity streams are enabled and publishes activities through kinesis, encrypted with a customer managed KMS key. I'm receiving the records in a lambda function with Nodejs runtime. For each record, I have encrypted and base64 encoded fields databaseActivityEvents and dataKey.

To decrypt it, the documentation at AWS says:

Take the following steps to decrypt the contents of the databaseActivityEvents field:

  1. Decrypt the value in the key JSON field using the KMS key you provided when starting database activity stream. Doing so returns the data encryption key in clear text.

  2. Base64-decode the value in the databaseActivityEvents JSON field to obtain the ciphertext, in binary format, of the audit payload.

  3. Decrypt the binary ciphertext with the data encryption key that you decoded in the first step.

  4. Decompress the decrypted payload.

For step 1, I can decrypt the data-key with following code:

import { KMSClient, DecryptCommand } from "@aws-sdk/client-kms"

...

const encryptedData = data.databaseActivityEvents
const encryptedKey = data.key

const inputKms = { 
  CiphertextBlob: new Buffer.from(encryptedKey, 'base64'),
  EncryptionContext: {'aws:rds:dbc-id': "cluster-[id]"}, 
}

const client = new KMSClient({ region: "us-east-1" })
const kmsCommand = new DecryptCommand(inputKms)
const kmsResponse = await client.send(kmsCommand)

const decryptedKey = kmsResponse.Plaintext

Step 2 is a basic conversion, too.

But I cannot accomplish step 3 whatever I do. How can I decrypt the data in nodejs/javascript?

Please note that, I cannot directly use native node crypto package because the data is in a format determined by the aws-encrption-sdk described here. It requires specific and complex handling, parsing which would be much harder. The solution should include the official aws-sdk which is aware of the format.

Here are some findings by me during the seek of the solution. I found a working java code in aws documentations that accomplishes the step 3:

private static byte[] decrypt(final byte[] decoded, final byte[] decodedDataKey) throws IOException {
    // Create a JCE master key provider using the random key and an AES-GCM encryption algorithm
    final JceMasterKey masterKey = JceMasterKey.getInstance(new SecretKeySpec(decodedDataKey, "AES"),
            "BC", "DataKey", "AES/GCM/NoPadding");
    try (final CryptoInputStream<JceMasterKey> decryptingStream = CRYPTO.createDecryptingStream(masterKey, new ByteArrayInputStream(decoded));
         final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
        IOUtils.copy(decryptingStream, out);
        return out.toByteArray();
    }
}

But I couldn't manage to find a similar straight forward usage in javascript version of aws-crypto package. Instead, decrypt methods require "keyring" objects. But there is a critical gap in the documentations, which is clearly insufficient, that how the content of activity streams plays with keyrings in this specific context.

I tried to use KmsKeyringNode as follows: (not sure the correct usage due to insufficient documentation)

export async function decryptByKmsKeyring(encDataBase64: string,  encDataKeyBase64: string) {
  const { decrypt } = buildClient(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT)
  const keyring = new KmsKeyringNode({
    generatorKeyId: 'arn:aws:kms:us-east-1:[account_id]:key/[key_id]',
    keyIds: [encDataKeyBase64]
  })
  const result = await decrypt(keyring, Buffer.from(encDataBase64, 'base64'))
  console.log("plaintext:", result.plaintext)
  console.log("messageHeader:", result.messageHeader)
  return result
}

But this got error "Malformed resource." Tried to troubleshoot it but couldn't find anything.

I also tried RawAesKeyringNode: (again, not sure the correct usage due to insufficient documentation)

export async function decryptRawAesKeyring(encDataBase64: string, key: Uint8Array) {
  const { decrypt } = buildClient(CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT)
  const wrappingSuite = RawAesWrappingSuiteIdentifier.AES256_GCM_IV12_TAG16_NO_PADDING
  const keyring = new RawAesKeyringNode({
    keyName: "BC",
    keyNamespace: "aws-kms",
    wrappingSuite,
    unencryptedMasterKey: key
  })
  const result = await decrypt(keyring, Buffer.from(encDataBase64, 'base64'))
  console.log("plaintext:", result.plaintext)
  console.log("messageHeader:", result.messageHeader)
  return result
}

But this got error "unencryptedDataKey has not been set." Tried to troubleshoot it but couldn't find anything.

How can I decrypt the data coming from activity streams in nodejs/javascript?

1 Answer
0

Not sure if databaseActivityEvents is indeed encrypted by AWS Encryption SDK but hope these docs help:

AWS Encryption SDK - How keyrings work? https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/using-keyrings.html

Example code for using keyring: https://github.com/aws/aws-encryption-sdk-javascript/blob/fd76d04e69920bdc49b57ccc498f75e9fd40ab23/modules/example-browser/src/kms_simple.ts#L42

AWS
answered 9 months ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions