diff --git a/lib/crypto.ts b/lib/crypto.ts new file mode 100644 index 0000000..ba93e43 --- /dev/null +++ b/lib/crypto.ts @@ -0,0 +1,87 @@ + +import crypto from "crypto"; + +// Strict Runtime Environment Verification Boundaries +const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; +const ALGORITHM = "aes-256-gcm"; +const IV_LENGTH = 12; // Standard IV size for GCM +const AUTH_TAG_LENGTH = 16; + +/** + * Validates and retrieves the operational encryption key buffer. + * Caches buffer derivations to prevent redundant allocations during high-frequency requests. + */ +let cachedKeyBuffer: Buffer | null = null; + +function getKeyBuffer(): Buffer { + if (cachedKeyBuffer) return cachedKeyBuffer; + + if (!ENCRYPTION_KEY) { + throw new Error("CRITICAL RUNTIME ERROR: 'ENCRYPTION_KEY' environment variable is unconfigured."); + } + + // Ensure the encryption key is exactly 32 bytes (256 bits) for aes-256-gcm + let keySource = ENCRYPTION_KEY; + if (keySource.length < 32) { + keySource = keySource.padEnd(32, "0"); // Safe local fallback padding + } else if (keySource.length > 32) { + keySource = keySource.substring(0, 32); // Truncate safely to matching bounds + } + + cachedKeyBuffer = Buffer.from(keySource, "utf-8"); + return cachedKeyBuffer; +} + +/** + * Encrypts a cleartext string using symmetric AES-256-GCM architecture. + * Outputs a consolidated hex payload string containing: iv + authTag + encryptedData + */ +export function encryptSecret(cleartext: string): string { + try { + if (!cleartext) return ""; + + const key = getKeyBuffer(); + const iv = crypto.randomBytes(IV_LENGTH); + const cipher = crypto.createCipheriv(ALGORITHM, key, iv); + + let encrypted = cipher.update(cleartext, "utf8", "hex"); + encrypted += cipher.final("hex"); + + const authTag = cipher.getAuthTag().toString("hex"); + + // Serialization format: [IV:24 hex chars][AuthTag:32 hex chars][EncryptedData...] + return `${iv.toString("hex")}:${authTag}:${encrypted}`; + } catch (error) { + console.error("Cryptographic Encryption Failure:", error); + throw new Error("Symmetric encryption routine encountered an serialization processing failure."); + } +} + +/** + * Decrypts a consolidated AES-256-GCM hex payload back into cleartext. + */ +export function decryptSecret(encryptedPayload: string): string { + try { + if (!encryptedPayload || !encryptedPayload.includes(":")) return ""; + + const [ivHex, authTagHex, encryptedText] = encryptedPayload.split(":"); + if (!ivHex || !authTagHex || !encryptedText) { + throw new Error("Invalid payload packaging layout context format."); + } + + const key = getKeyBuffer(); + const iv = Buffer.from(ivHex, "hex"); + const authTag = Buffer.from(authTagHex, "hex"); + + const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); + decipher.setAuthTag(authTag); + + let decrypted = decipher.update(encryptedText, "hex", "utf8"); + decrypted += decipher.final("utf8"); + + return decrypted; + } catch (error) { + console.error("Cryptographic Decryption Failure:", error); + throw new Error("Symmetric decryption routine failed — AuthTag mismatch or invalid configuration credentials."); + } +} \ No newline at end of file