noteJS-react/backend/utils/encryption.js

141 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};