/** * Client-side файловое шифрование для форм * Шифрует файлы в браузере пользователя перед отправкой */ class FileEncryption { constructor() { this.masterKey = 'sleeptrip_secure_key_2025'; // В продакшне должен быть более сложный this.encryptedFiles = new Map(); // Хранилище зашифрованных файлов } /** * Генерация криптографического ключа из мастер-пароля */ async generateKey(password, salt) { const encoder = new TextEncoder(); const keyMaterial = await crypto.subtle.importKey( 'raw', encoder.encode(password), 'PBKDF2', false, ['deriveKey'] ); return crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ); } /** * Шифрование файла */ async encryptFile(file) { try { // Генерируем соль и IV const salt = crypto.getRandomValues(new Uint8Array(16)); const iv = crypto.getRandomValues(new Uint8Array(12)); // Создаем ключ const key = await this.generateKey(this.masterKey, salt); // Читаем файл const fileBuffer = await file.arrayBuffer(); // Шифруем const encryptedData = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, fileBuffer ); // Создаем метаданные const metadata = { fileName: file.name, fileType: file.type, fileSize: file.size, encryptedAt: new Date().toISOString(), salt: Array.from(salt), iv: Array.from(iv) }; // Комбинируем метаданные и зашифрованные данные const metadataStr = JSON.stringify(metadata); const metadataBytes = new TextEncoder().encode(metadataStr); const metadataLength = new Uint32Array([metadataBytes.length]); const result = new Uint8Array( 4 + metadataBytes.length + encryptedData.byteLength ); result.set(new Uint8Array(metadataLength.buffer), 0); result.set(metadataBytes, 4); result.set(new Uint8Array(encryptedData), 4 + metadataBytes.length); return { encryptedData: result, metadata: metadata, originalName: file.name }; } catch (error) { console.error('Ошибка шифрования:', error); throw error; } } /** * Инициализация шифрования для input элемента */ initFileInput(inputId) { const input = document.getElementById(inputId); const wrapper = input.closest('.file-input-wrapper'); const textElement = wrapper.querySelector('.file-input-text'); // Создаем контейнер для статуса шифрования let statusDiv = wrapper.parentElement.querySelector('.encryption-status'); if (!statusDiv) { statusDiv = document.createElement('div'); statusDiv.className = 'encryption-status'; wrapper.parentElement.appendChild(statusDiv); } input.addEventListener('change', async (event) => { const file = event.target.files[0]; if (!file) return; try { // Показываем процесс шифрования this.showEncryptionStatus(statusDiv, 'encrypting', 'Шифрование файла...'); textElement.textContent = `Шифрую: ${file.name}`; textElement.className = 'file-input-text file-selected'; // Ждем немного для показа анимации await new Promise(resolve => setTimeout(resolve, 500)); // Шифруем файл const encryptedFile = await this.encryptFile(file); // Сохраняем зашифрованный файл this.encryptedFiles.set(inputId, encryptedFile); // Обновляем UI textElement.textContent = `${encryptedFile.originalName} (зашифрован)`; textElement.className = 'file-input-text file-encrypted'; wrapper.classList.add('file-encrypted'); this.showEncryptionStatus(statusDiv, 'encrypted', '✅ Файл зашифрован и готов к отправке'); // Создаем скрытое поле с зашифрованными данными this.createHiddenEncryptedField(input.form, inputId, encryptedFile); } catch (error) { console.error('Ошибка при шифровании файла:', error); this.showEncryptionStatus(statusDiv, 'error', '❌ Ошибка шифрования файла'); textElement.textContent = 'Выберите файл'; textElement.className = 'file-input-text'; wrapper.classList.remove('file-encrypted'); } }); } /** * Показ статуса шифрования */ showEncryptionStatus(statusDiv, type, message) { statusDiv.className = `encryption-status ${type}`; if (type === 'encrypting') { statusDiv.innerHTML = ` ${message} `; } else { statusDiv.textContent = message; } } /** * Создание скрытого поля с зашифрованными данными */ createHiddenEncryptedField(form, inputId, encryptedFile) { // Удаляем предыдущее скрытое поле если есть const existingField = form.querySelector(`input[name="${inputId}_encrypted"]`); if (existingField) { existingField.remove(); } // Создаем новое скрытое поле const hiddenField = document.createElement('input'); hiddenField.type = 'hidden'; hiddenField.name = `${inputId}_encrypted`; // Конвертируем зашифрованные данные в base64 const base64Data = btoa(String.fromCharCode(...encryptedFile.encryptedData)); hiddenField.value = base64Data; form.appendChild(hiddenField); // Также создаем поле с метаданными const metadataField = document.createElement('input'); metadataField.type = 'hidden'; metadataField.name = `${inputId}_metadata`; metadataField.value = JSON.stringify(encryptedFile.metadata); form.appendChild(metadataField); } /** * Получение зашифрованного файла по ID */ getEncryptedFile(inputId) { return this.encryptedFiles.get(inputId); } /** * Проверка наличия зашифрованных файлов перед отправкой формы */ validateEncryptedFiles(form) { const fileInputs = form.querySelectorAll('input[type="file"]'); const encryptedFields = form.querySelectorAll('input[name$="_encrypted"]'); for (let input of fileInputs) { if (input.files.length > 0) { const encryptedField = form.querySelector(`input[name="${input.id}_encrypted"]`); if (!encryptedField || !encryptedField.value) { alert('Не все файлы зашифрованы. Пожалуйста, дождитесь завершения шифрования.'); return false; } } } return true; } } // Инициализация при загрузке страницы document.addEventListener('DOMContentLoaded', function() { console.log('🔐 Инициализация системы шифрования...'); // Проверяем поддержку WebCrypto if (!window.crypto || !window.crypto.subtle) { console.error('❌ WebCrypto API не поддерживается в этом браузере'); alert('Ваш браузер не поддерживает шифрование. Используйте современный браузер.'); return; } const encryption = new FileEncryption(); // Инициализируем все file input элементы const fileInputs = document.querySelectorAll('input[type="file"]'); console.log(`📂 Найдено ${fileInputs.length} файловых input элементов`); fileInputs.forEach((input, index) => { if (input.id) { console.log(`🔧 Инициализируем шифрование для: ${input.id}`); encryption.initFileInput(input.id); } else { console.warn(`⚠️ File input ${index} не имеет ID, пропускаем`); } }); // Добавляем валидацию к формам перед отправкой const forms = document.querySelectorAll('form'); forms.forEach(form => { form.addEventListener('submit', function(event) { console.log('📤 Отправка формы, проверяем шифрование...'); if (!encryption.validateEncryptedFiles(form)) { event.preventDefault(); return false; } }); }); // Делаем encryption доступным глобально для отладки window.fileEncryption = encryption; console.log('✅ Система шифрования инициализирована'); });