// Credentials encryption/decryption using Web Crypto API document.addEventListener('DOMContentLoaded', function() { const decryptButtons = document.querySelectorAll('.decrypt-btn'); decryptButtons.forEach(button => { button.addEventListener('click', async function() { const id = this.dataset.id; const encryptedDiv = document.getElementById('encrypted-' + id); const decryptedDiv = document.getElementById('decrypted-' + id); // Get master password const password = prompt('Введите мастер-пароль:'); if (!password) return; try { const encryptedBase64 = encryptedDiv.textContent.trim(); const decrypted = await decrypt(encryptedBase64, password); // Parse and display decrypted content const data = JSON.parse(decrypted); let html = ''; for (const [key, value] of Object.entries(data)) { const label = formatLabel(key); html += `
${label}: ${value}
`; } decryptedDiv.innerHTML = html; decryptedDiv.style.display = 'block'; encryptedDiv.style.display = 'none'; this.textContent = 'Скрыть'; this.classList.remove('btn-primary'); this.classList.add('btn-secondary'); // Change button to toggle this.onclick = function() { if (decryptedDiv.style.display === 'none') { decryptedDiv.style.display = 'block'; encryptedDiv.style.display = 'none'; this.textContent = 'Скрыть'; } else { decryptedDiv.style.display = 'none'; encryptedDiv.style.display = 'block'; this.textContent = 'Расшифровать'; } }; } catch (error) { console.error('Decryption error:', error); alert('Ошибка расшифровки. Проверьте мастер-пароль.'); } }); }); }); function formatLabel(key) { const labels = { 'user': 'Пользователь', 'password': 'Пароль', 'host': 'Хост', 'port': 'Порт', 'database': 'База данных', 'notes': 'Заметки', 'ip': 'IP', 'auth': 'Авторизация', 'vpn': 'VPN' }; return labels[key] || key; } // Derive encryption key from password using PBKDF2 async function deriveKey(password, salt) { const encoder = new TextEncoder(); const keyMaterial = await crypto.subtle.importKey( 'raw', encoder.encode(password), { name: 'PBKDF2' }, false, ['deriveBits', 'deriveKey'] ); return crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ); } // Decrypt data async function decrypt(encryptedBase64, password) { const encryptedData = Uint8Array.from(atob(encryptedBase64), c => c.charCodeAt(0)); // Extract salt (first 16 bytes), IV (next 12 bytes), and ciphertext const salt = encryptedData.slice(0, 16); const iv = encryptedData.slice(16, 28); const ciphertext = encryptedData.slice(28); const key = await deriveKey(password, salt); const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, key, ciphertext ); return new TextDecoder().decode(decrypted); } // Encrypt data (for creating new credentials) async function encrypt(plaintext, password) { const encoder = new TextEncoder(); const salt = crypto.getRandomValues(new Uint8Array(16)); const iv = crypto.getRandomValues(new Uint8Array(12)); const key = await deriveKey(password, salt); const ciphertext = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, encoder.encode(plaintext) ); // Combine salt + iv + ciphertext const combined = new Uint8Array(16 + 12 + ciphertext.byteLength); combined.set(salt, 0); combined.set(iv, 16); combined.set(new Uint8Array(ciphertext), 28); return btoa(String.fromCharCode(...combined)); } // Utility function for encrypting credentials (can be used in console) window.encryptCreds = async function(data, password) { const json = JSON.stringify(data); return await encrypt(json, password); };