62 lines
2.6 KiB
TypeScript
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))
|
|
}
|