37 lines
1.7 KiB
JavaScript
37 lines
1.7 KiB
JavaScript
export async function deriveKey(passphrase, saltBytes) {
|
|
const enc = new TextEncoder();
|
|
const keyMaterial = await crypto.subtle.importKey("raw", enc.encode(passphrase), { name: "PBKDF2" }, false, ["deriveKey"]);
|
|
return crypto.subtle.deriveKey(
|
|
{ name: "PBKDF2", salt: saltBytes, iterations: 120_000, hash: "SHA-256" },
|
|
keyMaterial,
|
|
{ name: "AES-GCM", length: 256 },
|
|
false,
|
|
["encrypt", "decrypt"]
|
|
);
|
|
}
|
|
export async function encryptString(plaintext, passphrase) {
|
|
const enc = new TextEncoder();
|
|
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
const key = await deriveKey(passphrase, salt);
|
|
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, enc.encode(plaintext));
|
|
const version = new Uint8Array([1]);
|
|
const out = new Uint8Array(1 + 16 + 12 + ct.byteLength);
|
|
out.set(version, 0); out.set(salt, 1); out.set(iv, 17); out.set(new Uint8Array(ct), 29);
|
|
return out;
|
|
}
|
|
export async function decryptToString(payload, passphrase) {
|
|
const dec = new TextDecoder();
|
|
if (!(payload instanceof Uint8Array)) payload = new Uint8Array(payload);
|
|
if (payload.length < 29) throw new Error("ciphertext too short");
|
|
if (payload[0] !== 1) throw new Error("unknown version");
|
|
const salt = payload.slice(1, 17), iv = payload.slice(17, 29), ct = payload.slice(29);
|
|
const key = await deriveKey(passphrase, salt);
|
|
const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ct);
|
|
return dec.decode(pt);
|
|
}
|
|
export function toBlob(data) {
|
|
if (data instanceof Uint8Array) return new Blob([data], { type: "application/octet-stream" });
|
|
return new Blob([data], { type: "application/json;charset=utf-8" });
|
|
}
|