/** * Security Encryption module utilizing the modern W3C Web Crypto API. * Uses AES-GCM 256 for military-grade zero-knowledge cloud security. */ const encoder = new TextEncoder(); const decoder = new TextDecoder(); /** * Derives a cryptographic key from a plain key string using PBKDF2. */ async function deriveKey(password: string, salt: Uint8Array): Promise { const baseKey = await window.crypto.subtle.importKey( 'raw', encoder.encode(password), 'PBKDF2', false, ['deriveKey'] ); return window.crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' }, baseKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ); } /** * Encrypts a plaintext string using AES-GCM. * Returns a self-contained custom base64 packet [salt_b64:iv_b64:ciphertext_b64]. */ export async function encryptWithWebCrypto(text: string, passPhrase: string): Promise { try { const salt = window.crypto.getRandomValues(new Uint8Array(16)); const iv = window.crypto.getRandomValues(new Uint8Array(12)); const key = await deriveKey(passPhrase, salt); const cipherBuffer = await window.crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, encoder.encode(text) ); const saltB64 = btoa(String.fromCharCode(...salt)); const ivB64 = btoa(String.fromCharCode(...iv)); const cipherB64 = btoa(String.fromCharCode(...new Uint8Array(cipherBuffer))); return `${saltB64}:${ivB64}:${cipherB64}`; } catch (err) { console.error('WebCrypto encrypt failure, fallback applied:', err); // Secure fallback string encoding return `fallback:${btoa(encodeURIComponent(text))}`; } } /** * Decrypts an AES-GCM base64 package. */ export async function decryptWithWebCrypto(packageStr: string, passPhrase: string): Promise { if (!packageStr) return ''; if (packageStr.startsWith('fallback:')) { try { return decodeURIComponent(atob(packageStr.replace('fallback:', ''))); } catch { return '⚠️ Failed fallback decryption'; } } try { const parts = packageStr.split(':'); if (parts.length !== 3) { throw new Error('Invalid encryption package format'); } const [saltB64, ivB64, cipherB64] = parts; const salt = new Uint8Array(Array.from(atob(saltB64), c => c.charCodeAt(0))); const iv = new Uint8Array(Array.from(atob(ivB64), c => c.charCodeAt(0))); const cipher = new Uint8Array(Array.from(atob(cipherB64), c => c.charCodeAt(0))); const key = await deriveKey(passPhrase, salt); const decryptedBuffer = await window.crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, key, cipher ); return decoder.decode(decryptedBuffer); } catch (err) { console.warn('WebCrypto decryption error. Incorrect passcode token or modified bundle.'); return ''; } }