← back to Govarbitrage

src/lib/crypto.ts

41 lines

import crypto from "node:crypto";

// AES-256-GCM encryption for secrets at rest (e.g. provider API keys stored in
// ApiCredential). ENCRYPTION_KEY must be 32 bytes as 64 hex chars.

export interface Encrypted {
  ciphertext: string; // base64
  iv: string; // base64
  authTag: string; // base64
}

function key(): Buffer {
  const hex = process.env.ENCRYPTION_KEY || "";
  const buf = Buffer.from(hex, "hex");
  if (buf.length !== 32) {
    throw new Error("ENCRYPTION_KEY must be 32 bytes (64 hex chars) for AES-256-GCM");
  }
  return buf;
}

export function encryptSecret(plaintext: string): Encrypted {
  const iv = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv("aes-256-gcm", key(), iv);
  const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
  return {
    ciphertext: ct.toString("base64"),
    iv: iv.toString("base64"),
    authTag: cipher.getAuthTag().toString("base64"),
  };
}

export function decryptSecret(enc: Encrypted): string {
  const decipher = crypto.createDecipheriv("aes-256-gcm", key(), Buffer.from(enc.iv, "base64"));
  decipher.setAuthTag(Buffer.from(enc.authTag, "base64"));
  const pt = Buffer.concat([
    decipher.update(Buffer.from(enc.ciphertext, "base64")),
    decipher.final(),
  ]);
  return pt.toString("utf8");
}