const log_lvl = 0; // 0 = no logging, 1 = some logging, 2 = all logging // Updated 2025-05-08 async function generate_iv() { const data = new Uint8Array(16); crypto.getRandomValues(data); return data; } // Updated 2025-05-08 export const encrypt_content = async function encrypt_content( content: string, keyData: string ) { const iv = await generate_iv(); const keyBytes = await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode(keyData) ); const key = await crypto.subtle.importKey( 'raw', keyBytes, { name: 'AES-CBC' }, false, ['encrypt'] ); const encodedContent = await crypto.subtle.encrypt( { name: 'AES-CBC', iv: iv.buffer as ArrayBuffer }, key, new TextEncoder().encode(content).buffer as ArrayBuffer ); const base64 = btoa(String.fromCharCode(...new Uint8Array(encodedContent))); if (log_lvl) { console.log(`IV: ${iv}; Encrypted:`, base64); } return { base64, iv }; }; // Updated 2025-05-08 export const combine_iv_and_base64 = function combine_iv_and_base64( base64: string, iv: Uint8Array ) { if (log_lvl) { console.log(`IV: ${iv}; Encrypted:`, base64); } // Combine the IV and encrypted content const combined = Array.from(iv) .map((byte) => byte.toString(16).padStart(2, '0')) .join('') + ':' + base64; if (log_lvl) { console.log('Combined IV and Base64:', combined); } // const ivBase64 = btoa(String.fromCharCode(...iv)); // const combined = `${ivBase64}:${base64}`; // console.log('Combined IV and Base64 v2:', combined); return combined; }; // Updated 2025-05-08 export const encrypt_wrapper = async function encrypt_wrapper( content: string, keyData: string ) { if (!content) { console.error('No content provided. Returning empty string.'); return ''; } if (!keyData) { console.error('No keyData provided. Returning empty string.'); return ''; } const { base64, iv } = await encrypt_content(content, keyData); const combined = combine_iv_and_base64(base64, iv); return combined; }; // This does not handle errors (invalid key/password) well. // Updated 2025-05-08 export const decrypt_content = async function decrypt_content( base64Content: string, iv: Uint8Array, keyData: string ) { const keyBytes = await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode(keyData) ); const key = await crypto.subtle.importKey( 'raw', keyBytes, { name: 'AES-CBC' }, false, ['decrypt'] ); const encryptedContent = Uint8Array.from(atob(base64Content), (c) => c.charCodeAt(0) ); const decryptedContent = await crypto.subtle.decrypt( { name: 'AES-CBC', iv: iv.buffer as ArrayBuffer }, key, encryptedContent.buffer as ArrayBuffer ); const decodedContent = new TextDecoder().decode(decryptedContent); // console.log('Decrypted Content:', decodedContent); return decodedContent; }; // Updated 2025-05-08 export const split_iv_and_base64 = function split_iv_and_base64( combined: string ) { if (!combined) { console.error('No combined string provided. Returning empty object.'); return { iv: new Uint8Array(), base64: '' }; } const [iv_hex, encrypted_base64_string] = combined.split(':'); const base64 = encrypted_base64_string; const match_result = iv_hex.match(/.{1,2}/g); const iv = new Uint8Array( (match_result || []).map((byte) => parseInt(byte, 16)) ); if (log_lvl) { console.log(`IV: ${iv}; Encrypted:`, base64); } return { iv, base64 }; }; // Updated 2025-05-15 export const decrypt_wrapper = async function decrypt_wrapper( combined: string, keyData: string ) { if (!combined) { console.error('No combined string provided. Returning empty string.'); return false; } const { iv, base64 } = split_iv_and_base64(combined); console.log(`IV: ${iv}; Encrypted:`, base64); let decrypted; try { decrypted = await decrypt_content(base64, iv, keyData); if (log_lvl > 1) { console.log(`IV: ${iv}; Decrypted:`, decrypted); } else if (log_lvl) { console.log(`IV: ${iv}`); } } catch (error) { console.error('Decryption failed:', error); return false; } return decrypted; };