import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto' export interface EncryptedBlob { iv: string ciphertext: string tag: string } const ALGO = 'aes-256-gcm' const IV_BYTES = 12 function getKey(): Buffer { const hex = process.env.CREDENTIAL_ENCRYPTION_KEY if (!hex) { throw new Error('CREDENTIAL_ENCRYPTION_KEY env var is not set — cannot encrypt/decrypt stored credentials') } const key = Buffer.from(hex, 'hex') if (key.length !== 32) { throw new Error('CREDENTIAL_ENCRYPTION_KEY must be 32 bytes (64 hex chars), got ' + key.length + ' bytes') } return key } export function encryptPassword(plain: string): EncryptedBlob { const key = getKey() const iv = randomBytes(IV_BYTES) const cipher = createCipheriv(ALGO, key, iv) const ciphertext = Buffer.concat([cipher.update(plain, 'utf8'), cipher.final()]) const tag = cipher.getAuthTag() return { iv: iv.toString('hex'), ciphertext: ciphertext.toString('hex'), tag: tag.toString('hex'), } } export function decryptPassword(blob: EncryptedBlob): string { const key = getKey() const iv = Buffer.from(blob.iv, 'hex') const tag = Buffer.from(blob.tag, 'hex') const ciphertext = Buffer.from(blob.ciphertext, 'hex') const decipher = createDecipheriv(ALGO, key, iv) decipher.setAuthTag(tag) const plain = Buffer.concat([decipher.update(ciphertext), decipher.final()]) return plain.toString('utf8') }