/** * 凭证密封 —— ADR-009。app 层持有主密钥,模块只见 `SecretBox` 端口。 * * 密文格式:`v1...`(均 base64url)。 */ import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto'; import type { SecretBox } from '@modules/llm'; const DEV_SALT = 'jcai-dev-only-not-for-production'; export function loadSecretKey(env: NodeJS.ProcessEnv = process.env): Buffer { const raw = env['JCAI_SECRET_KEY']; if (raw !== undefined && raw !== '') { const key = Buffer.from(raw, 'base64'); if (key.length !== 32) throw new Error('JCAI_SECRET_KEY 必须是 32 字节的 base64'); return key; } if (env['NODE_ENV'] === 'production') { throw new Error('生产环境必须设置 JCAI_SECRET_KEY(ADR-009)'); } process.stderr.write('⚠️ JCAI_SECRET_KEY 未设置,使用开发派生密钥 —— 切勿用于生产(ADR-009)\n'); return createHash('sha256').update(DEV_SALT).digest(); } export function createSecretBox(key: Buffer): SecretBox { return { seal(plaintext) { const iv = randomBytes(12); const cipher = createCipheriv('aes-256-gcm', key, iv); const enc = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); return ['v1', iv.toString('base64url'), cipher.getAuthTag().toString('base64url'), enc.toString('base64url')].join('.'); }, open(text) { const [version, ivRaw, tagRaw, cipherRaw] = text.split('.'); if (version !== 'v1' || ivRaw === undefined || tagRaw === undefined || cipherRaw === undefined) { throw new Error('密文格式非法'); } const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(ivRaw, 'base64url')); decipher.setAuthTag(Buffer.from(tagRaw, 'base64url')); return Buffer.concat([decipher.update(Buffer.from(cipherRaw, 'base64url')), decipher.final()]).toString('utf8'); }, }; }