From adb1868899e6c6e6958e9e5b416a1788265a0d10 Mon Sep 17 00:00:00 2001 From: shaw Date: Sat, 26 Jul 2025 12:37:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=AF=86=E9=92=A5=E5=8A=A0=E5=AF=86=E7=9A=84?= =?UTF-8?q?=20Gemini=20=E8=B4=A6=E6=88=B7=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 检测使用默认密钥加密的数据 - 尝试用默认密钥解密后重新用当前密钥加密 - 解决密钥变更导致的解密失败问题 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- scripts/fix-gemini-default-key.js | 182 ++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 scripts/fix-gemini-default-key.js diff --git a/scripts/fix-gemini-default-key.js b/scripts/fix-gemini-default-key.js new file mode 100644 index 00000000..82f93ab1 --- /dev/null +++ b/scripts/fix-gemini-default-key.js @@ -0,0 +1,182 @@ +#!/usr/bin/env node + +/** + * 修复使用默认密钥加密的 Gemini 账户数据 + */ + +const path = require('path'); +const dotenv = require('dotenv'); +const crypto = require('crypto'); + +// 加载环境变量 +dotenv.config({ path: path.join(__dirname, '..', '.env') }); + +const redis = require('../src/models/redis'); +const logger = require('../src/utils/logger'); +const config = require('../config/config'); + +const ALGORITHM = 'aes-256-cbc'; +const IV_LENGTH = 16; +const GEMINI_ACCOUNT_KEY_PREFIX = 'gemini_account:'; +const ENCRYPTION_SALT = 'gemini-encryption-salt-2024'; + +// 默认密钥(可能在创建时使用) +const DEFAULT_KEY = 'default-encryption-key-change-in-production'; + +// 生成加密密钥(使用默认密钥) +function generateDefaultKey() { + return crypto.scryptSync(DEFAULT_KEY, ENCRYPTION_SALT, 32); +} + +// 生成加密密钥(使用当前配置) +function generateCurrentKey() { + return crypto.scryptSync(config.security.encryptionKey, ENCRYPTION_SALT, 32); +} + +// 尝试使用指定密钥解密 +function decryptWithKey(text, key) { + if (!text) return ''; + try { + // IV 是固定长度的 32 个十六进制字符(16 字节) + const ivHex = text.substring(0, 32); + const encryptedHex = text.substring(33); // 跳过冒号 + + const iv = Buffer.from(ivHex, 'hex'); + const encryptedText = Buffer.from(encryptedHex, 'hex'); + const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); + let decrypted = decipher.update(encryptedText); + decrypted = Buffer.concat([decrypted, decipher.final()]); + return decrypted.toString(); + } catch (error) { + return null; + } +} + +// 使用当前密钥加密 +function encryptWithCurrentKey(text) { + if (!text) return ''; + const key = generateCurrentKey(); + const iv = crypto.randomBytes(IV_LENGTH); + const cipher = crypto.createCipheriv(ALGORITHM, key, iv); + let encrypted = cipher.update(text); + encrypted = Buffer.concat([encrypted, cipher.final()]); + return iv.toString('hex') + ':' + encrypted.toString('hex'); +} + +async function fixGeminiDefaultKey() { + try { + console.log('🚀 开始修复使用默认密钥加密的 Gemini 账户...\n'); + + // 显示密钥信息 + console.log('📋 密钥信息:'); + console.log(` 默认密钥: ${DEFAULT_KEY}`); + console.log(` 当前密钥: ${config.security.encryptionKey}`); + console.log(` 密钥相同: ${DEFAULT_KEY === config.security.encryptionKey ? '是' : '否'}`); + console.log(); + + // 连接 Redis + console.log('📡 连接 Redis...'); + await redis.connect(); + console.log('✅ Redis 连接成功\n'); + + const client = redis.getClient(); + const keys = await client.keys(`${GEMINI_ACCOUNT_KEY_PREFIX}*`); + + if (keys.length === 0) { + console.log('❌ 没有找到 Gemini 账户'); + process.exit(1); + } + + console.log(`🔍 找到 ${keys.length} 个 Gemini 账户\n`); + + let fixedCount = 0; + + for (const key of keys) { + const accountData = await client.hgetall(key); + const accountId = key.replace(GEMINI_ACCOUNT_KEY_PREFIX, ''); + + console.log(`\n📋 处理账户: ${accountData.name} (${accountId})`); + + if (!accountData.refreshToken) { + console.log(' ⚠️ 无 refreshToken,跳过'); + continue; + } + + // 生成两种密钥 + const defaultKey = generateDefaultKey(); + const currentKey = generateCurrentKey(); + + // 先尝试用当前密钥解密 + console.log(' 🔐 尝试使用当前密钥解密...'); + let decryptedToken = decryptWithKey(accountData.refreshToken, currentKey); + + if (decryptedToken && decryptedToken.startsWith('1//')) { + console.log(' ✅ 当前密钥解密成功,无需修复'); + continue; + } + + // 尝试用默认密钥解密 + console.log(' 🔐 尝试使用默认密钥解密...'); + decryptedToken = decryptWithKey(accountData.refreshToken, defaultKey); + + if (!decryptedToken || !decryptedToken.startsWith('1//')) { + console.log(' ❌ 两种密钥都无法解密!'); + console.log(' 💡 可能需要手动更新 refresh token'); + continue; + } + + console.log(' ✅ 默认密钥解密成功!'); + console.log(` 📝 Token 前缀: ${decryptedToken.substring(0, 10)}...`); + + if (process.argv.includes('--fix')) { + // 使用当前密钥重新加密 + console.log(' 🔄 使用当前密钥重新加密...'); + + const updates = {}; + + // 重新加密 refreshToken + updates.refreshToken = encryptWithCurrentKey(decryptedToken); + + // 同样处理 accessToken 和 geminiOauth + if (accountData.accessToken) { + const decryptedAccess = decryptWithKey(accountData.accessToken, defaultKey); + if (decryptedAccess) { + updates.accessToken = encryptWithCurrentKey(decryptedAccess); + } + } + + if (accountData.geminiOauth) { + const decryptedOauth = decryptWithKey(accountData.geminiOauth, defaultKey); + if (decryptedOauth) { + updates.geminiOauth = encryptWithCurrentKey(decryptedOauth); + } + } + + // 更新 Redis + await client.hmset(key, updates); + console.log(' ✅ 已重新加密并保存'); + fixedCount++; + } else { + console.log(' ⚠️ 使用 --fix 参数来修复此账户'); + } + } + + console.log('\n' + '='.repeat(60)); + + if (process.argv.includes('--fix')) { + console.log(`\n✅ 修复完成!共修复 ${fixedCount} 个账户`); + } else { + console.log('\n💡 提示:使用 --fix 参数运行脚本以修复问题'); + console.log(' node scripts/fix-gemini-default-key.js --fix'); + } + + } catch (error) { + console.error('❌ 修复失败:', error); + } finally { + await redis.disconnect(); + process.exit(0); + } +} + +// 运行修复 +fixGeminiDefaultKey(); \ No newline at end of file