86 lines
2.2 KiB
TypeScript
86 lines
2.2 KiB
TypeScript
const ITERATIONS = 100000;
|
|
const SALT_LENGTH = 16;
|
|
const IV_LENGTH = 12;
|
|
|
|
const enc = new TextEncoder();
|
|
const dec = new TextDecoder();
|
|
|
|
export const encrypt = async (data: string, password: string) => {
|
|
const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
|
|
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
|
|
const passwordKey = await getPasswordKey(password);
|
|
const aesKey = await deriveKey(passwordKey, salt, ['encrypt']);
|
|
const encryptedContent = await crypto.subtle.encrypt(
|
|
{
|
|
name: 'AES-GCM',
|
|
iv: iv,
|
|
},
|
|
aesKey,
|
|
enc.encode(data)
|
|
);
|
|
|
|
const encryptedContentArr = new Uint8Array(encryptedContent);
|
|
const buf = new Uint8Array(
|
|
salt.byteLength + iv.byteLength + encryptedContentArr.byteLength
|
|
);
|
|
buf.set(salt, 0);
|
|
buf.set(iv, salt.byteLength);
|
|
buf.set(encryptedContentArr, salt.byteLength + iv.byteLength);
|
|
return bufToBase64(buf);
|
|
};
|
|
|
|
export const decrypt = async (encryptedData: string, password: string) => {
|
|
const encryptedDataBuff = base64ToBuf(encryptedData);
|
|
const salt = encryptedDataBuff.slice(0, SALT_LENGTH);
|
|
const iv = encryptedDataBuff.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
const data = encryptedDataBuff.slice(SALT_LENGTH + IV_LENGTH);
|
|
const passwordKey = await getPasswordKey(password);
|
|
const aesKey = await deriveKey(passwordKey, salt, ['decrypt']);
|
|
const decryptedContent = await crypto.subtle.decrypt(
|
|
{
|
|
name: 'AES-GCM',
|
|
iv: iv,
|
|
},
|
|
aesKey,
|
|
data
|
|
);
|
|
return dec.decode(decryptedContent);
|
|
};
|
|
|
|
const getPasswordKey = (password: string) => {
|
|
return crypto.subtle.importKey('raw', enc.encode(password), 'PBKDF2', false, [
|
|
'deriveKey',
|
|
]);
|
|
};
|
|
|
|
const deriveKey = (
|
|
passwordKey: CryptoKey,
|
|
salt: Uint8Array,
|
|
keyUsages: KeyUsage[]
|
|
) => {
|
|
return crypto.subtle.deriveKey(
|
|
{
|
|
name: 'PBKDF2',
|
|
salt: salt,
|
|
iterations: ITERATIONS,
|
|
hash: 'SHA-256',
|
|
},
|
|
passwordKey,
|
|
{ name: 'AES-GCM', length: 256 },
|
|
false,
|
|
keyUsages
|
|
);
|
|
};
|
|
|
|
const bufToBase64 = (buf: Uint8Array) => {
|
|
let s = '';
|
|
buf.forEach((c) => {
|
|
s += String.fromCharCode(c);
|
|
});
|
|
return btoa(s);
|
|
};
|
|
|
|
const base64ToBuf = (b64: string) => {
|
|
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
|
};
|