How To Use Encrypted Secrets With Vercel

👳🏽‍♂️

Ekam Singh / February 06, 2020

3 min read

When trying to deploy a new API route with Vercel, I stumbled onto this error.

Environment variables must not be greater
than 4kb when JSON-stringified (got 4.21KB)

I love Vercel and use it for all my deployments. Thus, I had 12 different secrets. Unfortunately, there is an upstream limit on the size.

The total size of environment variables is limited to 4kb. Deployments made with environment variables exceeding the 4kb limit will fail at the build step.

Workaround

The root issue with the size of my environment variables was due to large base64 encoded private keys from service accounts (e.g., Firebase, Google).

To work around this limit, we can encrypt our service account using AES encryption. If you've never worked with encryption before, don't worry – it's easier than it sounds.

Storing Your Secrets

We do not want to commit secrets to git. Thus, we should use an environment variable. We can add secrets using the Vercel CLI.

$ vc secret add google-encryption-iv
$ vc secret add google-encryption-secret

To reference it locally when using vc dev it needs to be added to your .env file.

.env
GOOGLE_ENCRYPTION_IV=042d344042d34404
GOOGLE_ENCRYPTION_KEY=042d344042d34404

Finally, we need to add our new secret to vercel.json.

vercel.json
{
  "env": {
    "GOOGLE_ENCRYPTION_IV": "@google-encryption-iv",
    "GOOGLE_ENCRYPTION_KEY": "@google-encryption-secret"
  }
}

We can now reference the secrets via process.env.GOOGLE_ENCRYPTION_IV and process.env.GOOGLE_ENCRYPTION_KEY.

Encryption Example

Let's assume we have a service account like this:

{
  "type": "service_account",
  "project_id": "...",
  "private_key_id": "...",
  "private_key": "...",
  "client_email": "...",
  "client_id": "...",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "..."
}

One way to encrypt this value is by using an AES Online Encryption Tool.

  • Enter your unencrypted text (or select a file).
  • Choose "CBC" mode.
  • Key Size -> 128.
  • IV -> random string of 16 characters. (Save this)
  • Secret -> random string of 16 characters. (Save this)
  • Output as base64 and hit "Encrypt."

You should now have three values: IV, secret, and the encrypted output. First, let's save the encrypted output to a file. We will commit this to git and deploy it as part of our application.

Including the base64 encrypted result with your deployed application is what reduces the size of the environment variable. You now only need 32 characters to decode your service account.

Create a new file called service-account.enc.js.

module.exports = { encrypted: 'BASE64_RESULT_GOES_HERE' };

Next, we need to create a function to decrypt the value.

import crypto from 'crypto';

import { encrypted } from './service-account.enc';

const algorithm = 'aes-128-cbc';
const decipher = crypto.createDecipheriv(
  algorithm,
  process.env.GOOGLE_ENCRYPTION_KEY,
  process.env.GOOGLE_ENCRYPTION_IV
);

export const getDecryptedSecret = () => {
  let decrypted = decipher.update(encrypted, 'base64', 'utf8');

  decrypted += decipher.final('utf8');

  return JSON.parse(decrypted);
};

Finally, we can use this decrypted value inside of our API.

import { google } from 'googleapis';

import { getDecryptedSecret } from './decret-secret';

const googleAuth = new google.auth.GoogleAuth({
  credentials: getDecryptedSecret(),
  scopes: ['https://www.googleapis.com/auth/youtube.readonly']
});

export default googleAuth;

Subscribe to the newsletter

Get emails from me about web development, tech, and early access to new articles.

- subscribers – 28 issues