'use strict'; const crypto = require('crypto'); const ALGORITHM = 'aes-256-gcm'; const IV_BYTES = 12; const TAG_BYTES = 16; function getKey() { const raw = process.env.TOKEN_ENCRYPTION_KEY || ''; if (!raw) throw new Error('TOKEN_ENCRYPTION_KEY is not set'); const buf = Buffer.from(raw, 'utf8'); if (buf.length < 32) throw new Error('TOKEN_ENCRYPTION_KEY must be at least 32 bytes'); return crypto.createHash('sha256').update(buf).digest(); } function assertEncryptionReady() { getKey(); } function encryptSecret(plaintext) { const key = getKey(); const iv = crypto.randomBytes(IV_BYTES); const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: TAG_BYTES }); const ct = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); const tag = cipher.getAuthTag(); return `${iv.toString('hex')}:${tag.toString('hex')}:${ct.toString('hex')}`; } function decryptSecret(stored) { const parts = stored.split(':'); if (parts.length !== 3) throw new Error('Invalid encrypted secret format'); const [ivHex, tagHex, ctHex] = parts; const key = getKey(); const iv = Buffer.from(ivHex, 'hex'); const tag = Buffer.from(tagHex, 'hex'); const ct = Buffer.from(ctHex, 'hex'); const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: TAG_BYTES }); decipher.setAuthTag(tag); return Buffer.concat([decipher.update(ct), decipher.final()]).toString('utf8'); } module.exports = { assertEncryptionReady, encryptSecret, decryptSecret };