feat(安全): 添加 API Key 加密存储功能
- 使用 AES-256-GCM 算法加密 API Key - 支持加密/解密/检测功能 - 兼容旧的明文存储数据
This commit is contained in:
parent
269fc798aa
commit
2e8033a8ae
88
src/lib/crypto.ts
Normal file
88
src/lib/crypto.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import crypto from 'crypto';
|
||||
|
||||
// 加密密钥(32字节 = 256位,用于 AES-256)
|
||||
// 生产环境应使用环境变量
|
||||
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || 'lioncode-encryption-key-2024-sec';
|
||||
|
||||
// 确保密钥长度为32字节
|
||||
const getKey = (): Buffer => {
|
||||
const key = ENCRYPTION_KEY;
|
||||
// 使用 SHA-256 哈希确保密钥长度为32字节
|
||||
return crypto.createHash('sha256').update(key).digest();
|
||||
};
|
||||
|
||||
// IV 长度(AES-256-GCM 推荐使用12字节)
|
||||
const IV_LENGTH = 12;
|
||||
|
||||
// Auth Tag 长度
|
||||
const AUTH_TAG_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* 加密 API Key
|
||||
* 使用 AES-256-GCM 加密算法
|
||||
* @param plainText 明文 API Key
|
||||
* @returns 加密后的字符串(格式:iv:authTag:encryptedData,Base64编码)
|
||||
*/
|
||||
export function encryptApiKey(plainText: string): string {
|
||||
if (!plainText) return '';
|
||||
|
||||
const key = getKey();
|
||||
const iv = crypto.randomBytes(IV_LENGTH);
|
||||
|
||||
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
||||
|
||||
let encrypted = cipher.update(plainText, 'utf8', 'base64');
|
||||
encrypted += cipher.final('base64');
|
||||
|
||||
const authTag = cipher.getAuthTag();
|
||||
|
||||
// 组合 IV + AuthTag + 加密数据,用冒号分隔
|
||||
return `${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密 API Key
|
||||
* @param encryptedText 加密后的字符串
|
||||
* @returns 解密后的明文 API Key
|
||||
*/
|
||||
export function decryptApiKey(encryptedText: string): string {
|
||||
if (!encryptedText) return '';
|
||||
|
||||
try {
|
||||
const parts = encryptedText.split(':');
|
||||
if (parts.length !== 3) {
|
||||
// 兼容旧的明文存储(迁移期间)
|
||||
console.warn('检测到未加密的 API Key,返回原值');
|
||||
return encryptedText;
|
||||
}
|
||||
|
||||
const [ivBase64, authTagBase64, encrypted] = parts;
|
||||
|
||||
const key = getKey();
|
||||
const iv = Buffer.from(ivBase64, 'base64');
|
||||
const authTag = Buffer.from(authTagBase64, 'base64');
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
let decrypted = decipher.update(encrypted, 'base64', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
return decrypted;
|
||||
} catch (error) {
|
||||
console.error('API Key 解密失败:', error);
|
||||
// 如果解密失败,可能是旧的明文数据,返回原值
|
||||
return encryptedText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字符串是否已加密
|
||||
* @param text 待检查的字符串
|
||||
* @returns 是否已加密
|
||||
*/
|
||||
export function isEncrypted(text: string): boolean {
|
||||
if (!text) return false;
|
||||
const parts = text.split(':');
|
||||
return parts.length === 3;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user