File size: 1,850 Bytes
f2d1fea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | import crypto from "node:crypto";
const ALGORITHM = "aes-256-gcm";
const IV_LENGTH = 16;
const SALT_LENGTH = 16;
const KEY_LENGTH = 32;
const ITERATIONS = 100_000;
function getKey(secret: Buffer, salt: Buffer): Buffer {
return crypto.pbkdf2Sync(secret, salt, ITERATIONS, KEY_LENGTH, "sha256");
}
function getEncryptionKey(): Buffer {
const key = process.env.API_KEY_ENCRYPTION_KEY;
if (!key || key.length < 32) {
throw new Error(
"API_KEY_ENCRYPTION_KEY environment variable must be set (minimum 32 characters)",
);
}
return Buffer.from(key, "utf8");
}
export function encryptApiKey(plaintext: string): string {
const secret = getEncryptionKey();
const salt = crypto.randomBytes(SALT_LENGTH);
const iv = crypto.randomBytes(IV_LENGTH);
const key = getKey(secret, salt);
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const authTag = cipher.getAuthTag();
// Format: salt:iv:authTag:ciphertext (all base64)
return [
salt.toString("base64"),
iv.toString("base64"),
authTag.toString("base64"),
encrypted.toString("base64"),
].join(":");
}
export function decryptApiKey(encrypted: string): string {
const secret = getEncryptionKey();
const parts = encrypted.split(":");
if (parts.length !== 4) {
throw new Error("Invalid encrypted API key format");
}
const salt = Buffer.from(parts[0]!, "base64");
const iv = Buffer.from(parts[1]!, "base64");
const authTag = Buffer.from(parts[2]!, "base64");
const ciphertext = Buffer.from(parts[3]!, "base64");
const key = getKey(secret, salt);
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(authTag);
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
}
|