import { bytesToHex, hexToBytes } from './encoding' const PBKDF2_ITERATIONS = 100000 const PBKDF2_HASH = 'SHA-256' export async function generateKEK(): Promise { return globalThis.crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']) } export async function deriveKeyFromPhrase(phrase: string[]): Promise { const phraseString = phrase.join(' ') const encoder = new TextEncoder() const password = encoder.encode(phraseString) const saltBuffer = await globalThis.crypto.subtle.digest('SHA-256', password) const salt = new Uint8Array(saltBuffer).slice(0, 32) const keyMaterial = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey']) return globalThis.crypto.subtle.deriveKey( { name: 'PBKDF2', salt, iterations: PBKDF2_ITERATIONS, hash: PBKDF2_HASH }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ) } export async function exportKEK(kek: CryptoKey): Promise { const exported = await globalThis.crypto.subtle.exportKey('raw', kek) return new Uint8Array(exported) } export async function importKEK(keyBytes: Uint8Array): Promise { const buffer = new ArrayBuffer(keyBytes.length) new Uint8Array(buffer).set(keyBytes) return globalThis.crypto.subtle.importKey('raw', buffer, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']) } export async function encryptWithAesGcm(params: { key: CryptoKey; plaintext: Uint8Array }): Promise<{ iv: Uint8Array; ciphertext: Uint8Array }> { const iv = globalThis.crypto.getRandomValues(new Uint8Array(12)) const plaintext = new Uint8Array(params.plaintext) const encrypted = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv }, params.key, plaintext) return { iv, ciphertext: new Uint8Array(encrypted) } } export async function decryptWithAesGcm(params: { key: CryptoKey; iv: Uint8Array; ciphertext: Uint8Array }): Promise { const ivBuffer = new ArrayBuffer(params.iv.length) new Uint8Array(ivBuffer).set(params.iv) const cipherBuffer = new ArrayBuffer(params.ciphertext.length) new Uint8Array(cipherBuffer).set(params.ciphertext) const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: new Uint8Array(ivBuffer) }, params.key, cipherBuffer) return new Uint8Array(decrypted) } export function encodeKekBytesForStorage(bytes: Uint8Array): Uint8Array { const encoder = new TextEncoder() return encoder.encode(bytesToHex(bytes)) } export function decodeKekBytesFromStorage(payload: Uint8Array): Uint8Array { const decoder = new TextDecoder() return hexToBytes(decoder.decode(payload)) }