I believe you are base64 encoding the ciphertext when you do:
if (typeof ciphertext !== 'string') {
return this.toBase64(ciphertext);
}
but you are not reversing the encoding before calculating the crc32c.
I pulled this example together from sample code, it works correctly for me from Cloud Shell. (Sorry it's messy):
// On Cloud Shell, install ts first with:
// npm install -g typescript
// npm i @types/node
// npm i @google-cloud/kms
// npm i fast-crc32c
// Then to compile and run:
// tsc testcrc.ts && node testcrc.js
// Code adapted from https://cloud.google.com/kms/docs/encrypt-decrypt#kms-decrypt-symmetric-nodejs
const projectId = 'kms-test-1367';
const locationId = 'global';
const keyRingId = 'so-67778448';
const keyId = 'example';
const plaintextBuffer = Buffer.from('squeamish ossifrage');
// Imports the Cloud KMS library
const {KeyManagementServiceClient} = require('@google-cloud/kms');
const crc32c = require('fast-crc32c');
// Instantiates a client
const client = new KeyManagementServiceClient();
// Build the key name
const keyName = client.cryptoKeyPath(projectId, locationId, keyRingId, keyId);
// Optional, but recommended: compute plaintext's CRC32C.
async function encryptSymmetric() {
const plaintextCrc32c = crc32c.calculate(plaintextBuffer);
console.log(`Plaintext crc32c: ${plaintextCrc32c}`);
const [encryptResponse] = await client.encrypt({
name: keyName,
plaintext: plaintextBuffer,
plaintextCrc32c: {
value: plaintextCrc32c,
},
});
const ciphertext = encryptResponse.ciphertext;
// Optional, but recommended: perform integrity verification on encryptResponse.
// For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
// https://cloud.google.com/kms/docs/data-integrity-guidelines
if (!encryptResponse.verifiedPlaintextCrc32c) {
throw new Error('Encrypt: request corrupted in-transit');
}
if (
crc32c.calculate(ciphertext) !==
Number(encryptResponse.ciphertextCrc32c.value)
) {
throw new Error('Encrypt: response corrupted in-transit');
}
console.log(`Ciphertext: ${ciphertext.toString('base64')}`);
console.log(`Ciphertext crc32c: ${encryptResponse.ciphertextCrc32c.value}`)
return ciphertext;
}
async function decryptSymmetric(ciphertext) {
const cipherTextBuf = Buffer.from(await ciphertext);
const ciphertextCrc32c = crc32c.calculate(cipherTextBuf);
console.log(`Ciphertext crc32c: ${ciphertextCrc32c}`);
const [decryptResponse] = await client.decrypt({
name: keyName,
ciphertext: cipherTextBuf,
ciphertextCrc32c: {
value: ciphertextCrc32c,
},
});
// Optional, but recommended: perform integrity verification on decryptResponse.
// For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
// https://cloud.google.com/kms/docs/data-integrity-guidelines
if (
crc32c.calculate(decryptResponse.plaintext) !==
Number(decryptResponse.plaintextCrc32c.value)
) {
throw new Error('Decrypt: response corrupted in-transit');
}
const plaintext = decryptResponse.plaintext.toString();
console.log(`Plaintext: ${plaintext}`);
console.log(`Plaintext crc32c: ${decryptResponse.plaintextCrc32c.value}`)
return plaintext;
}
decryptSymmetric(encryptSymmetric());
You can see that it logs the crc32c several times. The correct crc32c for the example string, "squeamish ossifrage", is 870328919. The crc32c for the ciphertext will vary on every run.
To run this code yourself, you just need to point it at your project, region, key ring, and key (which should be a symmetric encryption key); hopefully comparing this code with your code's results will help you find the issue.
Thanks for using Google Cloud and Cloud KMS!