diff --git a/packages/backend/src/database/schema.registry.ts b/packages/backend/src/database/schema.registry.ts index e2a6d1d..84ddaa7 100644 --- a/packages/backend/src/database/schema.registry.ts +++ b/packages/backend/src/database/schema.registry.ts @@ -58,7 +58,7 @@ export const tableDefinitions: TableDefinition[] = [ }, { name: 'audit_logs', sql: schemaSql.createAuditLogsTableSQL }, // { name: 'api_keys', sql: schemaSql.createApiKeysTableSQL }, // Removed API Keys table from registry - { name: 'passkeys', sql: schemaSql.createPasskeysTableSQL }, + // { name: 'passkeys', sql: schemaSql.createPasskeysTableSQL }, // Removed Passkeys table from registry { name: 'notification_settings', sql: schemaSql.createNotificationSettingsTableSQL }, { name: 'users', sql: schemaSql.createUsersTableSQL }, diff --git a/packages/backend/src/utils/crypto.ts b/packages/backend/src/utils/crypto.ts index f863805..15cf82e 100644 --- a/packages/backend/src/utils/crypto.ts +++ b/packages/backend/src/utils/crypto.ts @@ -1,41 +1,103 @@ import crypto from 'crypto'; -import bcrypt from 'bcrypt'; +import bcrypt from 'bcrypt'; // Keep bcrypt import if needed elsewhere -// Function to hash a password +const algorithm = 'aes-256-gcm'; +const ivLength = 16; // GCM 推荐的 IV 长度为 12 或 16 字节 +const tagLength = 16; // GCM 认证标签长度 + +/** + * Internal helper to get and validate the encryption key buffer on demand. + */ +const getEncryptionKeyBuffer = (): Buffer => { + const keyEnv = process.env.ENCRYPTION_KEY; + if (!keyEnv) { + // This should ideally not happen due to initializeEnvironment in index.ts + console.error('错误:ENCRYPTION_KEY 环境变量未设置!'); + throw new Error('ENCRYPTION_KEY is not set.'); + } + try { + const keyBuffer = Buffer.from(keyEnv, 'hex'); + if (keyBuffer.length !== 32) { + console.error(`错误:加密密钥长度必须是 32 字节,当前长度为 ${keyBuffer.length}。`); + throw new Error('Invalid ENCRYPTION_KEY length.'); + } + return keyBuffer; + } catch (error) { + console.error('错误:无法将 ENCRYPTION_KEY 从 hex 解码为 Buffer:', error); + throw new Error('Failed to decode ENCRYPTION_KEY.'); + } +}; + +/** + * 加密文本 (例如连接密码) + * @param text - 需要加密的明文 + * @returns Base64 编码的字符串,格式为 "iv:encrypted:tag" (Note: Original code concatenates directly) + */ +export const encrypt = (text: string): string => { + try { + const encryptionKey = getEncryptionKeyBuffer(); // Get key on demand + const iv = crypto.randomBytes(ivLength); + const cipher = crypto.createCipheriv(algorithm, encryptionKey, iv); + const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]); + const tag = cipher.getAuthTag(); + // 将 iv、密文和认证标签组合并编码 (Original concatenation method) + return Buffer.concat([iv, encrypted, tag]).toString('base64'); + } catch (error) { + console.error('加密失败:', error); + throw new Error('加密过程中发生错误'); + } +}; + +/** + * 解密文本 + * @param encryptedText - Base64 编码的加密字符串 (Original concatenated format) + * @returns 解密后的明文 + */ +export const decrypt = (encryptedText: string): string => { + try { + const encryptionKey = getEncryptionKeyBuffer(); // Get key on demand + const data = Buffer.from(encryptedText, 'base64'); + if (data.length < ivLength + tagLength) { + throw new Error('无效的加密数据格式'); + } + + // 从组合数据中提取 iv、密文和认证标签 + const iv = data.slice(0, ivLength); + const encrypted = data.slice(ivLength, data.length - tagLength); + const tag = data.slice(data.length - tagLength); + + const decipher = crypto.createDecipheriv(algorithm, encryptionKey, iv); + decipher.setAuthTag(tag); // 设置认证标签以供验证 + + const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); + return decrypted.toString('utf8'); + } catch (error) { + console.error('解密失败:', error); + // 在实际应用中,解密失败通常意味着数据被篡改或密钥错误 + // 不应向客户端泄露具体错误细节 + throw new Error('解密过程中发生错误或数据无效'); + } +}; + +// --- Password Hashing (Keep if needed) --- export async function hashPassword(password: string): Promise { - const saltRounds = 10; // Adjust salt rounds as needed for security/performance balance + const saltRounds = 10; return bcrypt.hash(password, saltRounds); } - -// Function to compare a password with a hash export async function comparePassword(password: string, hash: string): Promise { return bcrypt.compare(password, hash); } -// Function to generate a secure random string (e.g., for session secrets, tokens) +// --- Secure Random String (Keep if needed) --- export function generateSecureRandomString(length: number = 32): string { return crypto.randomBytes(length).toString('hex'); } -// --- Added for WebAuthn --- - -/** - * Converts an ArrayBuffer or Buffer to a Base64URL-encoded string. - * @param buffer The ArrayBuffer or Buffer to convert. - * @returns The Base64URL-encoded string. - */ +// --- WebAuthn Base64URL Utilities (Can be removed if not needed elsewhere) --- export function bufferToBase64url(buffer: ArrayBuffer | Buffer): string { const buf = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer); return buf.toString('base64url'); } - -/** - * Converts a Base64URL-encoded string back to a Buffer. - * @param base64urlString The Base64URL-encoded string. - * @returns The corresponding Buffer. - */ export function base64urlToBuffer(base64urlString: string): Buffer { - // Pad the string if necessary, as base64url might omit padding - // Node.js Buffer.from handles base64url directly return Buffer.from(base64urlString, 'base64url'); }