gongxiangp2/lib/ai-model-crypto.ts

27 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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') }
}