Files
veridian/app/utils/crypto.ts
T

96 lines
2.5 KiB
TypeScript

export async function deriveKey(password: string, userId: string) {
const iterations = 1_000_000;
const saltBuffer = new TextEncoder().encode(userId);
const passwordBuffer = new TextEncoder().encode(password);
const baseKey = await window.crypto.subtle.importKey(
"raw",
passwordBuffer,
{ name: "PBKDF2" },
false,
["deriveKey"]
);
const derivedKey = await window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: saltBuffer,
iterations: iterations,
hash: "SHA-256",
},
baseKey,
{
name: "AES-GCM",
length: 256
},
true,
["encrypt", "decrypt"]
);
const encryptionKey = await crypto.subtle.exportKey(
'jwk',
derivedKey
);
return encryptionKey;
}
function generateIv() {
return window.crypto.getRandomValues(new Uint8Array(12));
}
export function uint8ArrayToBase64(bytes: Uint8Array<ArrayBuffer>) {
let binaryString = '';
for (let i = 0; i < bytes.byteLength; i++) {
binaryString += String.fromCharCode(bytes[i]!);
}
return window.btoa(binaryString);
}
export function base64ToUint8Array(base64: string) {
const binaryString = window.atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
export async function encryptData(key: CryptoKey, plaintext: string) {
const iv = generateIv();
const data = new TextEncoder().encode(plaintext);
const encryptedContent = await window.crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv },
key,
data
);
// Combine IV and Ciphertext into one Uint8Array for easy storage
const combined = new Uint8Array(iv.length + encryptedContent.byteLength);
combined.set(iv);
combined.set(new Uint8Array(encryptedContent), iv.length);
return combined;
}
export async function decrypt(key: CryptoKey, combinedData: Uint8Array) {
try {
const iv = combinedData.slice(0, 12);
const ciphertext = combinedData.slice(12);
const decryptedBuffer = await window.crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv },
key,
ciphertext
);
return new TextDecoder().decode(decryptedBuffer);
} catch (error) {
throw new Error("Decryption failed. Incorrect password or corrupted data.");
}
}