146 lines
4.8 KiB
JavaScript
146 lines
4.8 KiB
JavaScript
// 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 += `<div class="field-row">
|
|
<span class="field-label">${label}:</span>
|
|
<span class="field-value">${value}</span>
|
|
</div>`;
|
|
}
|
|
|
|
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);
|
|
};
|