From f597c9b1b85ab12c5b61429534587e698dd1e61e Mon Sep 17 00:00:00 2001 From: digua Date: Mon, 18 May 2026 19:07:22 +0800 Subject: [PATCH] chore(electron): remove dead code crypto.ts and device-key.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit encryptApiKey/decryptApiKey and device key management have zero importers — legacy functionality already handled by @openchatlab/config/migrations/crypto-legacy. Removes ~278 lines. --- electron/main/ai/llm/crypto.ts | 181 ----------------------------- electron/main/ai/llm/device-key.ts | 97 ---------------- 2 files changed, 278 deletions(-) delete mode 100644 electron/main/ai/llm/crypto.ts delete mode 100644 electron/main/ai/llm/device-key.ts diff --git a/electron/main/ai/llm/crypto.ts b/electron/main/ai/llm/crypto.ts deleted file mode 100644 index 7e188c4c..00000000 --- a/electron/main/ai/llm/crypto.ts +++ /dev/null @@ -1,181 +0,0 @@ -/** - * API Key 加密工具 - * 使用 AES-256-GCM 加密,密钥从持久化的设备密钥派生 - */ - -import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'crypto' -import { execSync } from 'child_process' -import { getDeviceKey, getAllDeviceKeys } from './device-key' - -// 加密算法 -const ALGORITHM = 'aes-256-gcm' -// 加密前缀,用于标识已加密的数据 -const ENCRYPTED_PREFIX = 'enc:' -// 盐值,用于密钥派生(应用级别唯一) -const SALT = 'chatlab-api-key-encryption-v1' - -/** - * 从设备密钥派生加密密钥 - */ -function deriveKey(): Buffer { - const deviceKey = getDeviceKey() - return createHash('sha256') - .update(deviceKey + SALT) - .digest() -} - -/** - * 从旧的 machine-id 方案派生密钥(用于迁移) - * 尝试读取系统 machine-id,如果失败则尝试 fallback key - */ -function deriveLegacyKeys(): Buffer[] { - const keys: Buffer[] = [] - try { - const platform = process.platform - let cmd: string | null = null - if (platform === 'linux') { - cmd = '( cat /var/lib/dbus/machine-id /etc/machine-id 2> /dev/null || hostname ) | head -n 1 || :' - } else if (platform === 'darwin') { - cmd = 'ioreg -rd1 -c IOPlatformExpertDevice' - } else if (platform === 'win32') { - cmd = 'REG.exe QUERY HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography /v MachineGuid' - } - - if (cmd) { - const raw = execSync(cmd).toString() - let machineId: string - if (platform === 'darwin') { - machineId = - raw - .split('IOPlatformUUID')[1] - ?.split('\n')[0] - ?.replace(/[=\s"]/g, '') - ?.toLowerCase() || '' - } else if (platform === 'win32') { - machineId = - raw - .split('REG_SZ')[1] - ?.replace(/[\r\n\s]/g, '') - ?.toLowerCase() || '' - } else { - machineId = raw.replace(/[\r\n\s]/g, '').toLowerCase() - } - - if (machineId) { - // node-machine-id 默认会对 machine-id 做一次 sha256 hash - const hashed = createHash('sha256').update(machineId).digest('hex') - keys.push( - createHash('sha256') - .update(hashed + SALT) - .digest() - ) - } - } - } catch { - // 系统命令失败,忽略 - } - - // 旧版 fallback key - keys.push( - createHash('sha256') - .update('chatlab-fallback-key' + SALT) - .digest() - ) - return keys -} - -// 缓存密钥 -let cachedKey: Buffer | null = null - -function getKey(): Buffer { - if (!cachedKey) { - cachedKey = deriveKey() - } - return cachedKey -} - -/** - * 加密 API Key - * @param plaintext 明文 API Key - * @returns 加密后的字符串,格式: enc:iv:authTag:ciphertext - */ -export function encryptApiKey(plaintext: string): string { - if (!plaintext) return '' - - const key = getKey() - const iv = randomBytes(12) // GCM 推荐 12 字节 IV - - const cipher = createCipheriv(ALGORITHM, key, iv) - let encrypted = cipher.update(plaintext, 'utf8', 'base64') - encrypted += cipher.final('base64') - - const authTag = cipher.getAuthTag() - - // 格式: enc:iv:authTag:ciphertext - return `${ENCRYPTED_PREFIX}${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted}` -} - -/** - * 用指定密钥尝试解密 - */ -function tryDecryptWithKey(encrypted: string, key: Buffer): string | null { - try { - const parts = encrypted.slice(ENCRYPTED_PREFIX.length).split(':') - if (parts.length !== 3) return null - - const [ivBase64, authTagBase64, ciphertext] = parts - const iv = Buffer.from(ivBase64, 'base64') - const authTag = Buffer.from(authTagBase64, 'base64') - - const decipher = createDecipheriv(ALGORITHM, key, iv) - decipher.setAuthTag(authTag) - - let decrypted = decipher.update(ciphertext, 'base64', 'utf8') - decrypted += decipher.final('utf8') - - return decrypted - } catch { - return null - } -} - -/** - * 解密 API Key - * 优先使用当前密钥,失败时尝试旧版 machine-id 密钥(自动迁移) - * @param encrypted 加密后的字符串 - * @returns 解密后的明文,如果解密失败返回空字符串 - */ -export function decryptApiKey(encrypted: string): string { - if (!encrypted) return '' - - if (!isEncrypted(encrypted)) { - return encrypted - } - - // 1. 尝试所有 device key 文件(当前 + Electron legacy 位置) - const allDeviceKeys = getAllDeviceKeys() - for (const dk of allDeviceKeys) { - const derived = createHash('sha256') - .update(dk + SALT) - .digest() - const result = tryDecryptWithKey(encrypted, derived) - if (result !== null) return result - } - - // 2. 尝试旧版 machine-id 密钥 - const legacyKeys = deriveLegacyKeys() - for (const legacyKey of legacyKeys) { - const legacyResult = tryDecryptWithKey(encrypted, legacyKey) - if (legacyResult !== null) return legacyResult - } - - console.error('[Crypto] Failed to decrypt API Key with all available keys') - return '' -} - -/** - * 检查字符串是否是加密格式 - */ -export function isEncrypted(value: string): boolean { - return value?.startsWith(ENCRYPTED_PREFIX) ?? false -} diff --git a/electron/main/ai/llm/device-key.ts b/electron/main/ai/llm/device-key.ts deleted file mode 100644 index f96e6f01..00000000 --- a/electron/main/ai/llm/device-key.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * 设备密钥管理 - * 在应用数据目录下持久化一个随机生成的设备密钥,用于 API Key 加密。 - * 替代 node-machine-id,解决 Linux ARM64 等环境下 machine-id 不可用或不稳定的问题。 - */ - -import * as fs from 'fs' -import * as os from 'os' -import * as path from 'path' -import { randomBytes } from 'crypto' -import { getSystemDataDir, getElectronLegacyDataDir, ensureDir } from '../../paths' - -const DEVICE_KEY_FILE = '.device-key' - -let cachedDeviceKey: string | null = null - -function readKeyFromFile(keyPath: string): string | null { - try { - if (fs.existsSync(keyPath)) { - const key = fs.readFileSync(keyPath, 'utf-8').trim() - if (key.length >= 32) return key - } - } catch { - // ignore - } - return null -} - -/** - * 获取设备密钥(32 字节随机值的 hex 字符串) - * 首次调用时从文件读取,文件不存在则生成并写入。 - */ -export function getDeviceKey(): string { - if (cachedDeviceKey) return cachedDeviceKey - - const dataDir = getSystemDataDir() - ensureDir(dataDir) - const keyPath = path.join(dataDir, DEVICE_KEY_FILE) - - const key = readKeyFromFile(keyPath) - if (key) { - cachedDeviceKey = key - return cachedDeviceKey - } - - const newKey = randomBytes(32).toString('hex') - try { - fs.writeFileSync(keyPath, newKey, { encoding: 'utf-8', mode: 0o600 }) - } catch (error) { - console.error('[DeviceKey] Failed to write device key file:', error) - } - - cachedDeviceKey = newKey - return cachedDeviceKey -} - -/** - * 获取所有可能的 device key(当前 + Electron legacy 位置) - * 用于解密迁移前加密的 API Key - */ -export function getAllDeviceKeys(): string[] { - const keys: string[] = [] - const seen = new Set() - - const addKey = (k: string | null) => { - if (k && !seen.has(k)) { - seen.add(k) - keys.push(k) - } - } - - addKey(readKeyFromFile(path.join(getSystemDataDir(), DEVICE_KEY_FILE))) - - try { - addKey(readKeyFromFile(path.join(getElectronLegacyDataDir(), DEVICE_KEY_FILE))) - } catch { - // getElectronLegacyDataDir() may throw outside Electron - } - - if (process.platform === 'darwin') { - addKey( - readKeyFromFile(path.join(os.homedir(), 'Library', 'Application Support', 'ChatLab', 'data', DEVICE_KEY_FILE)) - ) - } else if (process.platform === 'win32') { - const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming') - addKey(readKeyFromFile(path.join(appData, 'ChatLab', 'data', DEVICE_KEY_FILE))) - } - - return keys -} - -/** - * 重置缓存(仅测试用) - */ -export function resetDeviceKeyCache(): void { - cachedDeviceKey = null -}