/** * Cryptographic utilities for secure storage * Uses Web Crypto API for AES-GCM encryption */ const SALT = new TextEncoder().encode('zclaw-secure-storage-salt'); const ITERATIONS = 100000; /** * Convert Uint8Array to base64 string */ export function arrayToBase64(array: Uint8Array): string { if (array.length === 0) return ''; let binary = ''; for (let i = 0; i < array.length; i++) { binary += String.fromCharCode(array[i]); } return btoa(binary); } /** * Convert base64 string to Uint8Array */ export function base64ToArray(base64: string): Uint8Array { if (!base64) return new Uint8Array([]); const binary = atob(base64); const array = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { array[i] = binary.charCodeAt(i); } return array; } /** * Derive an encryption key from a master key */ export async function deriveKey( masterKey: string, salt: Uint8Array = SALT ): Promise { const encoder = new TextEncoder(); const keyMaterial = await crypto.subtle.importKey( 'raw', encoder.encode(masterKey), 'PBKDF2', false, ['deriveBits', 'deriveKey'] ); return crypto.subtle.deriveKey( { name: 'PBKDF2', salt, iterations: ITERATIONS, hash: 'SHA-256', }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ); } /** * Encrypt data using AES-GCM */ export async function encrypt( plaintext: string, key: CryptoKey ): Promise<{ iv: string; data: string }> { const encoder = new TextEncoder(); const iv = crypto.getRandomValues(new Uint8Array(12)); const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, encoder.encode(plaintext) ); return { iv: arrayToBase64(iv), data: arrayToBase64(new Uint8Array(encrypted)), }; } /** * Decrypt data using AES-GCM */ export async function decrypt( encrypted: { iv: string; data: string }, key: CryptoKey ): Promise { const decoder = new TextDecoder(); const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: base64ToArray(encrypted.iv) }, key, base64ToArray(encrypted.data) ); return decoder.decode(decrypted); } /** * Generate a random master key for encryption */ export function generateMasterKey(): string { const array = crypto.getRandomValues(new Uint8Array(32)); return arrayToBase64(array); }