import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto' import { AiModelError } from './ai-model-access' export interface AiModelEncryptedKey { secretCiphertext: string; secretNonce: string; secretAuthTag: string } export function isAiModelEncryptionConfigured(): boolean { const secret = process.env.API_KEY_ENCRYPTION_KEY?.trim() return Boolean(secret && Buffer.byteLength(secret, 'utf8') >= 32) } function encryptionKey(): Buffer { if (!isAiModelEncryptionConfigured()) throw new AiModelError('请先在服务端配置 API_KEY_ENCRYPTION_KEY(至少 32 字节)', 503, 'encryption_not_configured') // Domain separation prevents this credential store sharing ciphertext keys with platform API keys. return createHash('sha256').update('gongxiangp2:ai-models:v1\0').update(process.env.API_KEY_ENCRYPTION_KEY!.trim()).digest() } export function encryptAiModelKey(value: string): AiModelEncryptedKey { const nonce = randomBytes(12) const cipher = createCipheriv('aes-256-gcm', encryptionKey(), nonce) return { secretCiphertext: Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]).toString('base64'), secretNonce: nonce.toString('base64'), secretAuthTag: cipher.getAuthTag().toString('base64') } } export function decryptAiModelKey(value: AiModelEncryptedKey): string { const key = encryptionKey() try { const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(value.secretNonce, 'base64')) decipher.setAuthTag(Buffer.from(value.secretAuthTag, 'base64')) return Buffer.concat([decipher.update(Buffer.from(value.secretCiphertext, 'base64')), decipher.final()]).toString('utf8') } catch { throw new AiModelError('模型密钥无法解密,请检查服务端加密配置或替换密钥', 503, 'credential_unavailable') } }