/** * Утилиты для шифрования/дешифрования данных * Использует AES-256-GCM для шифрования паролей */ const crypto = require('crypto'); // Ключ шифрования из переменной окружения (обязателен для работы с IPSec паролями) const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY; if (!ENCRYPTION_KEY) { console.warn('WARNING: ENCRYPTION_KEY is not set. IPSec password encryption/decryption will fail.'); console.warn('Please set ENCRYPTION_KEY environment variable (64 hex characters for AES-256).'); console.warn('You can generate a key with: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"'); } const ALGORITHM = 'aes-256-gcm'; /** * Получить ключ шифрования (32 байта) */ function getEncryptionKey() { if (!ENCRYPTION_KEY) { throw new Error('ENCRYPTION_KEY environment variable is not set. Cannot encrypt/decrypt IPSec passwords.'); } // Если ENCRYPTION_KEY - hex строка (64 символа для 32 байт), конвертируем в Buffer if (ENCRYPTION_KEY.length === 64) { try { return Buffer.from(ENCRYPTION_KEY, 'hex'); } catch (error) { throw new Error(`Invalid ENCRYPTION_KEY format: not a valid hex string. ${error.message}`); } } // Иначе используем как UTF-8 строку и дополняем/обрезаем до 32 байт const key = Buffer.from(ENCRYPTION_KEY, 'utf8'); if (key.length === 32) return key; // Дополняем или обрезаем до 32 байт const result = Buffer.alloc(32); key.copy(result, 0, 0, Math.min(key.length, 32)); return result; } /** * Зашифровать текст * @param {string} text - Текст для шифрования * @returns {string} - Зашифрованная строка в формате iv:authTag:encryptedData (все в base64) */ function encrypt(text) { if (!text) return ''; const key = getEncryptionKey(); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(ALGORITHM, key, iv); let encrypted = cipher.update(String(text), 'utf8', 'base64'); encrypted += cipher.final('base64'); const authTag = cipher.getAuthTag(); // Формат: iv:authTag:encryptedData (все в base64) return `${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted}`; } /** * Расшифровать текст * @param {string} encryptedText - Зашифрованная строка в формате iv:authTag:encryptedData * @returns {string} - Расшифрованный текст */ function decrypt(encryptedText) { if (!encryptedText) { throw new Error('Encrypted text is empty'); } if (typeof encryptedText !== 'string') { throw new Error('Encrypted text must be a string'); } try { const parts = encryptedText.split(':'); if (parts.length !== 3) { throw new Error(`Invalid encrypted format: expected 3 parts separated by ':', got ${parts.length}`); } const [ivBase64, authTagBase64, encryptedBase64] = parts; if (!ivBase64 || !authTagBase64 || !encryptedBase64) { throw new Error('Invalid encrypted format: one or more parts are empty'); } let iv, authTag; try { iv = Buffer.from(ivBase64, 'base64'); authTag = Buffer.from(authTagBase64, 'base64'); } catch (bufferError) { throw new Error(`Failed to decode base64: ${bufferError.message}`); } if (iv.length !== 16) { throw new Error(`Invalid IV length: expected 16 bytes, got ${iv.length}`); } if (authTag.length !== 16) { throw new Error(`Invalid auth tag length: expected 16 bytes, got ${authTag.length}`); } const key = getEncryptionKey(); const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); decipher.setAuthTag(authTag); let decrypted = decipher.update(encryptedBase64, 'base64', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } catch (error) { if (error.message.includes('Unsupported state') || error.message.includes('bad decrypt')) { throw new Error(`Decryption failed: possibly wrong encryption key or corrupted data. Original error: ${error.message}`); } console.error('Decryption error details:', { error: error.message, encryptedTextLength: encryptedText.length }); throw error; } } module.exports = { encrypt, decrypt, };