2024-07-21 02:58:59 +02:00
|
|
|
const IV_SIZE = 12;
|
|
|
|
|
const ENCRYPTION_ALG = "AES-GCM";
|
|
|
|
|
|
|
|
|
|
// minimal
|
|
|
|
|
export async function encryptString(
|
2026-04-01 08:29:39 +00:00
|
|
|
plaintext: string,
|
|
|
|
|
passphrase: string,
|
2024-07-21 02:58:59 +02:00
|
|
|
): Promise<string> {
|
2026-04-01 08:29:39 +00:00
|
|
|
if (!window.crypto.subtle) return plaintext;
|
2024-07-21 02:58:59 +02:00
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
const iv = generateIV();
|
|
|
|
|
const key = await importKey(passphrase, "encrypt");
|
|
|
|
|
const encryptedArray = await encrypt(iv, key, plaintext);
|
2024-07-21 02:58:59 +02:00
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
const encryptionData = {
|
|
|
|
|
iv: uInt8ToArray(iv),
|
|
|
|
|
encryptedArray: uInt8ToArray(encryptedArray),
|
|
|
|
|
};
|
|
|
|
|
return btoa(JSON.stringify(encryptionData));
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function decryptString(
|
2026-04-01 08:29:39 +00:00
|
|
|
cyphertext: string,
|
|
|
|
|
passphrase: string,
|
2024-07-21 02:58:59 +02:00
|
|
|
): Promise<string> {
|
2026-04-01 08:29:39 +00:00
|
|
|
try {
|
|
|
|
|
const encrypionData = JSON.parse(atob(cyphertext));
|
|
|
|
|
const iv = arrayToUint8(encrypionData.iv);
|
|
|
|
|
const encryptedArray = arrayToUint8(encrypionData.encryptedArray);
|
|
|
|
|
|
|
|
|
|
const key = await importKey(passphrase, "decrypt");
|
|
|
|
|
return await decrypt(iv, key, encryptedArray);
|
|
|
|
|
} catch {
|
|
|
|
|
return cyphertext;
|
|
|
|
|
}
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// process
|
2026-04-18 11:55:01 +00:00
|
|
|
function encode(string: string): Uint8Array {
|
2026-04-01 08:29:39 +00:00
|
|
|
return new TextEncoder().encode(string);
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 11:55:01 +00:00
|
|
|
function decode(array: Uint8Array): string {
|
2026-04-01 08:29:39 +00:00
|
|
|
return new TextDecoder("utf-8").decode(array);
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 11:55:01 +00:00
|
|
|
async function encrypt(
|
2026-04-01 08:29:39 +00:00
|
|
|
iv: Uint8Array,
|
|
|
|
|
key: CryptoKey,
|
|
|
|
|
message: string,
|
2024-07-21 02:58:59 +02:00
|
|
|
): Promise<Uint8Array> {
|
2026-04-01 08:29:39 +00:00
|
|
|
const arrayBuffer = await window.crypto.subtle.encrypt(
|
|
|
|
|
{ name: ENCRYPTION_ALG, iv },
|
|
|
|
|
key,
|
|
|
|
|
encode(message),
|
|
|
|
|
);
|
|
|
|
|
return new Uint8Array(arrayBuffer);
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 11:55:01 +00:00
|
|
|
async function decrypt(
|
2026-04-01 08:29:39 +00:00
|
|
|
iv: Uint8Array,
|
|
|
|
|
key: CryptoKey,
|
|
|
|
|
cyphertext: Uint8Array,
|
2024-07-21 02:58:59 +02:00
|
|
|
): Promise<string> {
|
2026-04-01 08:29:39 +00:00
|
|
|
const arrayBuffer = await crypto.subtle.decrypt(
|
|
|
|
|
{ name: ENCRYPTION_ALG, iv },
|
|
|
|
|
key,
|
|
|
|
|
cyphertext,
|
|
|
|
|
);
|
|
|
|
|
return arrayBufferToString(arrayBuffer);
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 11:55:01 +00:00
|
|
|
async function hash(encoded: Uint8Array): Promise<ArrayBuffer> {
|
2026-04-01 08:29:39 +00:00
|
|
|
return await crypto.subtle.digest("SHA-256", encoded);
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// iv
|
2026-04-18 11:55:01 +00:00
|
|
|
function generateIV(): Uint8Array {
|
2026-04-01 08:29:39 +00:00
|
|
|
return crypto.getRandomValues(new Uint8Array(IV_SIZE));
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 11:55:01 +00:00
|
|
|
function splitMergedString(mergedString: string): [Uint8Array, string] {
|
2026-04-01 08:29:39 +00:00
|
|
|
const iv = mergedString.slice(0, IV_SIZE);
|
|
|
|
|
const encryptedString = mergedString.slice(IV_SIZE);
|
2024-07-21 02:58:59 +02:00
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
return [JSON.parse(iv), encryptedString];
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// key
|
2026-04-18 11:55:01 +00:00
|
|
|
async function importKey(
|
2026-04-01 08:29:39 +00:00
|
|
|
passphrase: string,
|
|
|
|
|
purpose: "encrypt" | "decrypt",
|
2024-07-21 02:58:59 +02:00
|
|
|
): Promise<CryptoKey> {
|
2026-04-01 08:29:39 +00:00
|
|
|
return await crypto.subtle.importKey(
|
|
|
|
|
"raw",
|
|
|
|
|
await hash(encode(passphrase)),
|
|
|
|
|
{ name: ENCRYPTION_ALG },
|
|
|
|
|
false,
|
|
|
|
|
[purpose],
|
|
|
|
|
);
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// conversion
|
2026-04-18 11:55:01 +00:00
|
|
|
function arrayBufferToString(arrayBuffer: ArrayBuffer): string {
|
2026-04-01 08:29:39 +00:00
|
|
|
const uInt8Array = new Uint8Array(arrayBuffer);
|
|
|
|
|
return decode(uInt8Array);
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 11:55:01 +00:00
|
|
|
function uInt8ToArray(uInt8Array: Uint8Array): number[] {
|
2026-04-01 08:29:39 +00:00
|
|
|
return Array.from(uInt8Array);
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 11:55:01 +00:00
|
|
|
function arrayToUint8(array: number[]): Uint8Array {
|
2026-04-01 08:29:39 +00:00
|
|
|
return new Uint8Array(array);
|
2024-07-21 02:58:59 +02:00
|
|
|
}
|