export async function deriveKeyFromSecret(secret, salt = 'toolbox_salt_v1') { if (!secret || typeof secret !== 'string') { throw new Error('Invalid secret: must be a non-empty string'); } const enc = new TextEncoder(); const secretKey = await crypto.subtle.importKey( 'raw', enc.encode(secret), { name: 'PBKDF2' }, false, ['deriveKey'] ); const key = await crypto.subtle.deriveKey( { name: 'PBKDF2', salt: enc.encode(salt), iterations: 100000, hash: 'SHA-256' }, secretKey, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); return key; } function _randBytes(len = 12) { const b = new Uint8Array(len); crypto.getRandomValues(b); return b; } export async function encryptJSON(key, obj) { if (!key || !(key instanceof CryptoKey)) { throw new Error('Valid CryptoKey is required'); } const enc = new TextEncoder(); const iv = _randBytes(12); const plain = enc.encode(JSON.stringify(obj)); const cipher = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, plain ); const combined = new Uint8Array(iv.byteLength + cipher.byteLength); combined.set(iv, 0); combined.set(new Uint8Array(cipher), iv.byteLength); // Безопасное преобразование в base64 const binaryString = Array.from(combined, byte => String.fromCharCode(byte)).join(''); return btoa(binaryString); } export async function decryptToJSON(key, b64) { if (!key || !(key instanceof CryptoKey)) { throw new Error('Valid CryptoKey is required'); } if (!b64 || typeof b64 !== 'string') { throw new Error('Invalid base64 string'); } try { // Безопасное преобразование из base64 const binaryString = atob(b64); const raw = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { raw[i] = binaryString.charCodeAt(i); } const iv = raw.slice(0, 12); const cipher = raw.slice(12); const plainBuf = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, key, cipher ); const dec = new TextDecoder(); return JSON.parse(dec.decode(plainBuf)); } catch (error) { console.error('Decryption error:', error); throw new Error('Failed to decrypt data: ' + error.message); } }