import crypto from 'crypto' import { promisify } from 'util' const ALGORITHM = 'aes-256-gcm' const KEY_LENGTH = 32 const IV_LENGTH = 16 const _TAG_LENGTH = 16 const SALT_LENGTH = 32 // OWASP 2023 建议至少 600,000 次(针对 SHA256) const ITERATIONS = 600000 const CRYPTO_VERSION = 'v1' // 异步 PBKDF2 避免阻塞事件循环 const pbkdf2Async = promisify(crypto.pbkdf2) /** * 从密码异步派生密钥 * 使用异步版本避免阻塞 Electron 主进程事件循环 */ async function deriveKeyAsync(password: string, salt: Buffer): Promise { return pbkdf2Async(password, salt, ITERATIONS, KEY_LENGTH, 'sha512') } /** * 从密码同步派生密钥(仅在启动等必要时使用) */ function deriveKeySync(password: string, salt: Buffer): Buffer { return crypto.pbkdf2Sync(password, salt, ITERATIONS, KEY_LENGTH, 'sha512') } // 生成随机密钥(用于主密钥) export function generateMasterKey(): string { return crypto.randomBytes(KEY_LENGTH).toString('hex') } /** * 加密数据 * 格式: version:salt:iv:tag:encrypted (全部 hex) * version 用于未来加密方案升级时的安全迁移 */ export function encrypt(plaintext: string, password: string): string { const salt = crypto.randomBytes(SALT_LENGTH) const key = deriveKeySync(password, salt) const iv = crypto.randomBytes(IV_LENGTH) const cipher = crypto.createCipheriv(ALGORITHM, key, iv) const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]) const tag = cipher.getAuthTag() // 立即清零密钥内存 key.fill(0) // 格式: version:salt:iv:tag:encrypted (全部 hex) return [ CRYPTO_VERSION, salt.toString('hex'), iv.toString('hex'), tag.toString('hex'), encrypted.toString('hex'), ].join(':') } /** * 异步加密(推荐,不阻塞事件循环) */ export async function encryptAsync(plaintext: string, password: string): Promise { const salt = crypto.randomBytes(SALT_LENGTH) const key = await deriveKeyAsync(password, salt) const iv = crypto.randomBytes(IV_LENGTH) const cipher = crypto.createCipheriv(ALGORITHM, key, iv) const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]) const tag = cipher.getAuthTag() // 立即清零密钥内存 key.fill(0) return [ CRYPTO_VERSION, salt.toString('hex'), iv.toString('hex'), tag.toString('hex'), encrypted.toString('hex'), ].join(':') } /** * 解密数据 * 支持带版本标识的新格式和不带版本标识的旧格式(向后兼容) */ export function decrypt(ciphertext: string, password: string): string { try { const parts = ciphertext.split(':') // 判断格式:新格式有5部分(含version),旧格式有4部分 let saltHex: string, ivHex: string, tagHex: string, encryptedHex: string if (parts.length === 5) { // 新格式: version:salt:iv:tag:encrypted [, saltHex, ivHex, tagHex, encryptedHex] = parts } else if (parts.length === 4) { // 旧格式: salt:iv:tag:encrypted(向后兼容) [saltHex, ivHex, tagHex, encryptedHex] = parts } else { throw new Error('Invalid ciphertext format') } if (!saltHex || !ivHex || !tagHex || !encryptedHex) { throw new Error('Invalid ciphertext format') } const salt = Buffer.from(saltHex, 'hex') const iv = Buffer.from(ivHex, 'hex') const tag = Buffer.from(tagHex, 'hex') const encrypted = Buffer.from(encryptedHex, 'hex') const key = deriveKeySync(password, salt) const decipher = crypto.createDecipheriv(ALGORITHM, key, iv) decipher.setAuthTag(tag) const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]) // 清零密钥内存 key.fill(0) return decrypted.toString('utf8') } catch (error) { throw new Error('Decryption failed: ' + (error as Error).message) } } /** * 简单加密(使用主密钥,不经过 PBKDF2) * 用于内部数据加密,密钥长度必须正确 */ export function simpleEncrypt(plaintext: string, key: string): string { const iv = crypto.randomBytes(IV_LENGTH) const keyBuffer = Buffer.from(key, 'hex') // 验证密钥长度 if (keyBuffer.length !== KEY_LENGTH) { throw new Error(`Invalid key length: expected ${KEY_LENGTH} bytes, got ${keyBuffer.length}`) } const cipher = crypto.createCipheriv(ALGORITHM, keyBuffer, iv) const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]) const tag = cipher.getAuthTag() return iv.toString('hex') + ':' + tag.toString('hex') + ':' + encrypted.toString('hex') } /** * 简单解密 */ export function simpleDecrypt(ciphertext: string, key: string): string { try { const [ivHex, tagHex, encryptedHex] = ciphertext.split(':') if (!ivHex || !tagHex || !encryptedHex) { throw new Error('Invalid ciphertext format') } const iv = Buffer.from(ivHex, 'hex') const tag = Buffer.from(tagHex, 'hex') const encrypted = Buffer.from(encryptedHex, 'hex') const keyBuffer = Buffer.from(key, 'hex') // 验证密钥长度 if (keyBuffer.length !== KEY_LENGTH) { throw new Error(`Invalid key length: expected ${KEY_LENGTH} bytes, got ${keyBuffer.length}`) } const decipher = crypto.createDecipheriv(ALGORITHM, keyBuffer, iv) decipher.setAuthTag(tag) const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]) return decrypted.toString('utf8') } catch (error) { throw new Error('Decryption failed: ' + (error as Error).message) } }