/** * 服务端 AES-256-GCM 加解密工具(Node.js 环境) * * 使用 PBKDF2-HMAC-SHA256 从 ENCRYPTION_SECRET 派生密钥, * 与 crypto.ts 共享相同的算法和参数,实现前后端加解密互通。 * * 供 Next.js API Route 在服务端解密请求体、加密响应体。 */ import crypto from 'node:crypto'; const ALGORITHM = 'aes-256-gcm'; const IV_LENGTH = 12; const AUTH_TAG_LENGTH = 16; const PBKDF2_ITERATIONS = 100_000; const PBKDF2_SALT = 'novalon-website-crypto-salt-v1'; const KEY_LENGTH = 32; // 256 bits function getSecret(): string { const secret = process.env.ENCRYPTION_SECRET; if (!secret) { throw new Error('ENCRYPTION_SECRET 未配置,服务端加解密无法初始化'); } return secret; } let cachedKey: Buffer | null = null; function deriveKey(): Buffer { if (cachedKey) return cachedKey; const secret = getSecret(); cachedKey = crypto.pbkdf2Sync( secret, PBKDF2_SALT, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha256', ); return cachedKey; } /** * 加密明文,返回 Base64 编码的密文(12-byte-IV || AES-GCM-ciphertext) */ export function encrypt(plaintext: string): string { const key = deriveKey(); const iv = crypto.randomBytes(IV_LENGTH); const cipher = crypto.createCipheriv(ALGORITHM, key, iv); const encrypted = Buffer.concat([ cipher.update(plaintext, 'utf8'), cipher.final(), ]); const authTag = cipher.getAuthTag(); // 格式: iv + authTag + encrypted const combined = Buffer.concat([iv, authTag, encrypted]); return combined.toString('base64'); } /** * 解密 Base64 编码的密文,返回明文字符串 */ export function decrypt(encryptedBase64: string): string { const key = deriveKey(); const combined = Buffer.from(encryptedBase64, 'base64'); const iv = combined.subarray(0, IV_LENGTH); const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH); const encrypted = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH); const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); decipher.setAuthTag(authTag); const decrypted = Buffer.concat([ decipher.update(encrypted), decipher.final(), ]); return decrypted.toString('utf8'); } /** * 验证加密配置是否可用 */ export function isEncryptionAvailable(): boolean { return !!process.env.ENCRYPTION_SECRET; }