mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
chore(electron): remove dead code crypto.ts and device-key.ts
encryptApiKey/decryptApiKey and device key management have zero importers — legacy functionality already handled by @openchatlab/config/migrations/crypto-legacy. Removes ~278 lines.
This commit is contained in:
1 parent
249df9c8e3
commit
f597c9b1b8
2 files changed
-278
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -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<string>()
|
||||
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user