Search code examples
encryptiongoogle-cloud-storagegoogle-cloud-kms

How to decrypt file from Google Cloud Storage?


I have an encrypted file stored in a Google Cloud Storage bucket that was generated with the following command line:

gcloud kms encrypt --location=global --keyring=my-keyring --key=-my-key --plaintext-file=my-file --ciphertext-file=my-file.enc

I am now trying to decrypt such file in a Cloud Run service with the following code:

const kms = require('@google-cloud/kms');
const client = new kms.KeyManagementServiceClient();
const file = storage.bucket("my-bucket").file('my-file.enc');
const name = client.cryptoKeyPath( 'projectId', 'global', 'my-keyring', 'my-key' );
let encrypted = (await file.download())[0];
const [result] = await client.decrypt({name, encrypted });

I am getting the following error:

Error: Decryption failed: verify that 'name' refers to the correct CryptoKey.

Which, according to this, is misleading and should be considered as not being properly deciphered. I cannot shake the feeling that I am missing a base64 encode/decode somewhere but I don't seem to find the solution.

If I run the decryption from the command-line it works just fine.

Any help is very appreciated.

Thanks.

EDIT: Problem solved thanks to this awesome community. Here goes the steps to make this work, in case others face the same issue:

Encrypt the file using the following command line and upload it via the web UI.

gcloud kms encrypt --location=global --keyring=my-keyring --key=-my-key --plaintext-file=my-file --ciphertext-file=my-file.enc

Decrypt using the following code:

const kms = require('@google-cloud/kms');
const client = new kms.KeyManagementServiceClient();
const file = storage.bucket("my-bucket").file('my-file.enc');
const name = client.cryptoKeyPath( 'projectId', 'global', 'my-keyring', 'my-key' );
let encrypted = (await file.download())[0];
const ciphertext = encrypted .toString('base64');
const [result] = await client.decrypt({name, ciphertext});
console.log(Buffer.from(result.plaintext, 'base64').toString('utf8'))

Solution

  • I spot a few things here:

    1. Assuming your command is correct, my-file-enc should be my-file.enc instead (dot vs dash)

    2. Verify that projectId is being set correctly. If you're populating this from an environment variable, console.log and make sure it matches the project in which you created the KMS key. gcloud defaults to a project (you can figure out which project by running gcloud config list and checking the core/project attribute). If you created the key in project foo, but your Cloud Run service is looking in project bar, it will fail.

    3. When using --ciphertext-file to write to a file, the data is not base64 encoded. However, you are creating a binary file. How are you uploading that binary string to Cloud Storage? The most probable culprit seems to be an encoding problem (ASCII vs UTF) which could cause the decryption to fail. Make sure you are writing and reading the file as binary.

    4. Looking at the Cloud KMS Nodejs documentation, it specifies that the ciphertext should be "exactly as returned from the encrypt call". The documentation says that the KMS response is a base64 encoded string, so you could try base64 encoding your data in your Cloud Run service before sending it to Cloud KMS for decryption:

      let encrypted = (await file.download())[0];
      let encryptedEncoded = encrypted.toString('base64');
      const [result] = await client.decrypt({name, encrypted});
      
    5. You may want to take a look at Berglas, which automates this process. There are really good examples for Cloud Run with node.

    6. For more patterns, check out Secrets in Serverless.