Google Cloud KMS: The checksum in field ciphertext_crc32c did not match the data in field ciphertext

600 Views Asked by At

I am having issues setting up a system to encrypt and decrypt data in my Node.js backend. I am following this guide in the process.

I wrote a helper class KMSEncryption to abstract the logic from the example. Here's the code where I call it:

const kms = new KMSEncryption();
const textToEncrypt = 'hello world!';
const base64string = await kms.encrypt(textToEncrypt);
const decrypted = await kms.decrypt(base64string);

The issue I am having is that the decryption fails with the following error:

UnhandledPromiseRejectionWarning: Error: 3 INVALID_ARGUMENT: The checksum in field ciphertext_crc32c did not match the data in field ciphertext.

I tried to compare side by side with guide from Google docs but I cannot see where I went wrong.

Some of the things I have tried include:

  • Converting the base64string into a Buffer
  • Trying to calculate checksum on a Buffer of base64string and not the string itself

Any help is appreciated. Thank you

1

There are 1 best solutions below

0
On BEST ANSWER

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!