I am encoding and decoding passwords using a master password, salt and iv. The weird thing is whenever the error happens (during the decryption process, I never get an error during encryption) and I restart the vite server, the error is temporarily fixed. I have checked the iv and salt and they stay the same during the encryption and decryption process. The master password is also the same since the user has to enter it every single time.
This is my code:
const isBrowser =
typeof window !== "undefined" && typeof window.localStorage !== "undefined";
const getNewKey = async (password: string, salt: string) => {
if (isBrowser) {
const enc = new TextEncoder();
const baseKey = await crypto.subtle.importKey(
"raw",
enc.encode(password),
"PBKDF2",
false,
["deriveBits", "deriveKey"]
);
const params = {
name: "PBKDF2",
salt: base64ToArrayBuffer(salt),
iterations: 100000,
hash: "SHA-256",
};
return await crypto.subtle.deriveKey(
params,
baseKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
}
};
export const encryptData = async (
password: string,
salt: string,
data: string
) => {
const key = await getNewKey(password, salt);
if (!key) return "ERROR";
console.log("Generated key: ", key)
const iv = crypto.getRandomValues(new Uint8Array(12));
const encryptedData = arrayBufferToBase64(
await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv,
},
key,
new TextEncoder().encode(data)
)
);
return {
iv: uint8ArrayToBase64(iv),
encryptedData,
};
};
export const decryptData = async (
password: string,
salt: string,
data: string,
iv: string
) => {
console.log("IV: ", iv, " Salt: ", salt);
const key = await getNewKey(password, salt);
if (!key) return "ERROR";
console.log("Generated key: ", key)
const dataBuffer = base64ToArrayBuffer(data);
const ivArray = base64ToUint8Array(iv);
try {
const decryptedData = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: ivArray,
},
key,
dataBuffer
);
return new TextDecoder().decode(decryptedData);
} catch (error) {
console.error(error);
}
};
const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
let binary = "";
const bytes = new Uint8Array(buffer);
const length = bytes.byteLength;
for (let i = 0; i < length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
};
const base64ToArrayBuffer = (base64: string) => {
const binaryString = 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.buffer;
};
function uint8ArrayToBase64(uint8Array: Uint8Array) {
let binaryString = "";
const len = uint8Array.length;
for (let i = 0; i < len; i++) {
binaryString += String.fromCharCode(uint8Array[i]);
}
return btoa(binaryString);
}
function base64ToUint8Array(base64: string) {
const binaryString = atob(base64);
const len = binaryString.length;
const uint8Array = new Uint8Array(len);
for (let i = 0; i < len; i++) {
uint8Array[i] = binaryString.charCodeAt(i);
}
return uint8Array;
}