const crypto = require("crypto"); // Ключ шифрования из переменных окружения или дефолтный (для разработки) // В продакшене ОБЯЗАТЕЛЬНО установите ENCRYPTION_KEY в .env файле! // Ключ должен быть строкой минимум 32 символа для AES-256 const ENCRYPTION_KEY_STRING = process.env.ENCRYPTION_KEY || "default-key-change-in-production-min-32-chars"; // Проверка ключа при запуске const isProduction = process.env.NODE_ENV === "production"; const isUsingDefaultKey = !process.env.ENCRYPTION_KEY; // В продакшене требуем установленный ключ if (isProduction && isUsingDefaultKey) { console.error( "❌ ОШИБКА: ENCRYPTION_KEY не установлен в переменных окружения!" ); console.error( "Установите ENCRYPTION_KEY в .env файле перед запуском в продакшене." ); console.error("Пример: ENCRYPTION_KEY=ваш-случайный-ключ-минимум-32-символа"); process.exit(1); } // Предупреждение в разработке if (!isProduction && isUsingDefaultKey) { console.warn("⚠️ ПРЕДУПРЕЖДЕНИЕ: Используется дефолтный ключ шифрования!"); console.warn("Для безопасности установите ENCRYPTION_KEY в .env файле."); console.warn("Пример: ENCRYPTION_KEY=ваш-случайный-ключ-минимум-32-символа"); } // Преобразуем строку ключа в 32-байтовый ключ для AES-256 const ENCRYPTION_KEY = crypto .createHash("sha256") .update(ENCRYPTION_KEY_STRING) .digest(); // Длина IV (Initialization Vector) для AES-256-GCM const IV_LENGTH = 16; // Метка аутентификации для проверки целостности данных const AUTH_TAG_LENGTH = 16; /** * Шифрует текст заметки * @param {string} text - Текст для шифрования * @returns {string} - Зашифрованный текст в формате base64:iv:authTag */ function encrypt(text) { if (!text || text.trim() === "") { return text; } try { // Генерируем случайный IV для каждой операции шифрования const iv = crypto.randomBytes(IV_LENGTH); // Создаем шифр с алгоритмом AES-256-GCM const cipher = crypto.createCipheriv("aes-256-gcm", ENCRYPTION_KEY, iv); // Шифруем текст let encrypted = cipher.update(text, "utf8", "base64"); encrypted += cipher.final("base64"); // Получаем метку аутентификации для проверки целостности const authTag = cipher.getAuthTag(); // Возвращаем зашифрованные данные в формате: base64:iv:authTag return `${encrypted}:${iv.toString("base64")}:${authTag.toString( "base64" )}`; } catch (error) { console.error("Ошибка шифрования:", error); throw new Error("Ошибка шифрования данных"); } } /** * Дешифрует текст заметки * @param {string} encryptedText - Зашифрованный текст в формате base64:iv:authTag * @returns {string} - Расшифрованный текст */ function decrypt(encryptedText) { if (!encryptedText || encryptedText.trim() === "") { return encryptedText; } // Проверяем формат зашифрованных данных // Если формат не соответствует ожидаемому, возможно это старая незашифрованная заметка const parts = encryptedText.split(":"); if (parts.length !== 3) { // Если это не зашифрованный текст, возвращаем как есть (для обратной совместимости) console.warn("Обнаружен незашифрованный текст заметки"); return encryptedText; } try { const [encrypted, ivBase64, authTagBase64] = parts; // Преобразуем IV и метку аутентификации из base64 const iv = Buffer.from(ivBase64, "base64"); const authTag = Buffer.from(authTagBase64, "base64"); // Создаем дешифратор с алгоритмом AES-256-GCM const decipher = crypto.createDecipheriv("aes-256-gcm", ENCRYPTION_KEY, iv); // Устанавливаем метку аутентификации для проверки целостности decipher.setAuthTag(authTag); // Дешифруем текст let decrypted = decipher.update(encrypted, "base64", "utf8"); decrypted += decipher.final("utf8"); return decrypted; } catch (error) { console.error("Ошибка дешифрования:", error); // Если не удалось дешифровать, возможно это старая заметка // Возвращаем оригинальный текст return encryptedText; } } /** * Проверяет, является ли текст зашифрованным * @param {string} text - Текст для проверки * @returns {boolean} - true если текст зашифрован */ function isEncrypted(text) { if (!text || text.trim() === "") { return false; } const parts = text.split(":"); return parts.length === 3; } module.exports = { encrypt, decrypt, isEncrypted, };