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:
-
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.
-
Base64-decode the value in the databaseActivityEvents JSON field to
obtain the ciphertext, in binary format, of the audit payload.
-
Decrypt the binary ciphertext with the data encryption key that you
decoded in the first step.
-
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?