UNPKG

novel-writer-cn

Version:

AI 驱动的中文小说创作工具 - 基于结构化工作流的智能写作助手

175 lines (148 loc) 4.9 kB
/** * 解密器 * 负责在内存中解密从服务端获取的加密 Prompt * 安全原则:解密结果仅在内存中,永不持久化 */ import crypto from 'crypto'; export class Decryptor { constructor() { // 算法配置 this.algorithm = 'aes-256-gcm'; this.saltLength = 32; this.ivLength = 16; this.tagLength = 16; } /** * 解密加密的 Prompt * @param {Object} encryptedData - 包含加密数据的对象 * @param {string} encryptedData.encrypted - hex 编码的加密数据 * @param {string} encryptedData.iv - hex 编码的 IV * @param {string} encryptedData.authTag - hex 编码的认证标签 * @param {string} sessionKey - hex 格式的会话密钥 * @returns {string} 解密后的 Prompt(仅在内存中) */ async decrypt(encryptedData, sessionKey) { let decrypted = null; try { // 验证输入 if (!encryptedData || !encryptedData.encrypted || !encryptedData.iv || !encryptedData.authTag) { throw new Error('加密数据不完整'); } // 从 hex 格式转换为 Buffer const iv = Buffer.from(encryptedData.iv, 'hex'); const authTag = Buffer.from(encryptedData.authTag, 'hex'); const encrypted = Buffer.from(encryptedData.encrypted, 'hex'); const key = Buffer.from(sessionKey, 'hex'); // 验证数据完整性 if (iv.length !== this.ivLength) { throw new Error('IV 长度无效'); } if (authTag.length !== this.tagLength) { throw new Error('认证标签长度无效'); } if (key.length !== 32) { throw new Error('密钥长度无效'); } // 创建解密器 const decipher = crypto.createDecipheriv(this.algorithm, key, iv); decipher.setAuthTag(authTag); // 执行解密 let decrypted = decipher.update(encrypted, null, 'utf8'); decrypted += decipher.final('utf8'); // 验证解密结果 if (!this.isValidPrompt(decrypted)) { throw new Error('解密结果无效'); } // 立即返回,不存储 return decrypted; } catch (error) { // 清理可能的敏感数据 if (decrypted) { this.secureClear(decrypted); } // 根据错误类型提供有用的信息 if (error.message.includes('bad decrypt')) { throw new Error('解密失败:密钥无效或数据损坏'); } else if (error.message.includes('auth tag')) { throw new Error('解密失败:数据完整性验证失败'); } else { throw new Error(`解密失败: ${error.message}`); } } } /** * 验证解密的 Prompt 是否有效 * 基础验证,确保不是乱码 */ isValidPrompt(prompt) { if (!prompt || typeof prompt !== 'string') { return false; } // 检查是否包含基本的 Prompt 特征 // 这些特征应该是所有 Prompt 都有的通用部分 const hasValidStructure = prompt.length > 10 && // 至少有一定长度 prompt.includes('{{') && // 包含参数占位符 /[\u4e00-\u9fa5]/.test(prompt); // 包含中文字符 return hasValidStructure; } /** * 安全清理敏感数据 * 尽可能快地释放内存引用 */ secureClear(data) { if (!data) return; try { if (typeof data === 'string') { // 对于字符串,无法真正覆写,但可以释放引用 data = null; } else if (Buffer.isBuffer(data)) { // 对于 Buffer,可以填充零 data.fill(0); data = null; } else if (typeof data === 'object') { // 对于对象,递归清理 Object.keys(data).forEach(key => { this.secureClear(data[key]); delete data[key]; }); data = null; } } catch (e) { // 忽略清理错误 } } /** * 验证会话密钥格式 */ isValidSessionKey(sessionKey) { if (!sessionKey || typeof sessionKey !== 'string') { return false; } // 会话密钥应该是一个合理长度的字符串 return sessionKey.length >= 32 && sessionKey.length <= 256; } /** * 估算解密后的大小 * 用于内存管理 */ estimateDecryptedSize(encryptedData) { // Base64 编码会增加约 33% 的大小 // AES 加密可能会有填充 const base64Size = encryptedData.length; const estimatedSize = Math.floor(base64Size * 0.75); return estimatedSize; } /** * 检查是否可以安全解密 * 确保有足够的内存 */ canSafelyDecrypt(encryptedData) { const estimatedSize = this.estimateDecryptedSize(encryptedData); const memoryUsage = process.memoryUsage(); const availableMemory = 500 * 1024 * 1024; // 假设 500MB 上限 return (memoryUsage.heapUsed + estimatedSize) < availableMemory; } } // 导出单例 export const decryptor = new Decryptor();