96 lines
2.5 KiB
TypeScript
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.");
|
|
}
|
|
}
|