2026-01-13 15:56:14 +01:00

62 lines
2.6 KiB
TypeScript

import { bytesToHex, hexToBytes } from './encoding'
const PBKDF2_ITERATIONS = 100000
const PBKDF2_HASH = 'SHA-256'
export async function generateKEK(): Promise<CryptoKey> {
return globalThis.crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'])
}
export async function deriveKeyFromPhrase(phrase: string[]): Promise<CryptoKey> {
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<Uint8Array> {
const exported = await globalThis.crypto.subtle.exportKey('raw', kek)
return new Uint8Array(exported)
}
export async function importKEK(keyBytes: Uint8Array): Promise<CryptoKey> {
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<Uint8Array> {
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))
}