I'm having a security requirement not to send data as plain-text to the server and the credentials shouldn't be visible in the network tab of the browser. I could have used hashing but the catch is we already have old stored passwords with hash and salting applied. Now, not to disturb the old database, I was thinking to simply encrypt the data and decrypt it on server side (node).
Henceforth, I'm trying to use Web Crypto API to achieve the desired outcome. Below is a simple script I was trying to write before exporting the logic to both client and server environments :
function base64ToArrayBuffer(base64): ArrayBuffer {
return Uint8Array.from(atob(base64), c => c.charCodeAt(0))
}
function ArrayBufferToBase64(buffer): string {
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
}
async function encryptString(privateKey: string, publicKey: string, data: string) {
let params: AesGcmParams = {
name: 'AES-GCM',
iv: base64ToArrayBuffer(publicKey)
}
let keyImported: CryptoKey = await window.crypto.subtle.importKey("raw", base64ToArrayBuffer(privateKey), "AES-GCM", true, ["encrypt", "decrypt"]);
let dataInstance: ArrayBuffer = new TextEncoder().encode(data);
return Promise.resolve(await window.crypto.subtle.encrypt(params, keyImported, dataInstance));
}
async function decryptString(privateKey: string, publicKey: string, data: string) {
let params: AesGcmParams = {
name: 'AES-GCM',
iv: base64ToArrayBuffer(publicKey)
}
let keyImported: CryptoKey = await window.crypto.subtle.importKey("raw", base64ToArrayBuffer(privateKey), "AES-GCM", true, ["encrypt", "decrypt"]);
let dataInstance: ArrayBuffer = new TextEncoder().encode(data);
return Promise.resolve(await window.crypto.subtle.decrypt(params, keyImported, dataInstance));
}
// Key for encryption/decryption process
let PRIVATE_KEY: string = 'Fdz2z54vAwIjq0tginHyzgyL62q+f4sBlMi5IOOXJ7c=';
let PUBLIC_KEY: string = btoa(String.fromCharCode(...window.crypto.getRandomValues(new Uint8Array(12))));
// Password to be encrypted before being sent to server
let password: string = "Test@123";
(async () => {
let encrypted_data: string = ArrayBufferToBase64(await encryptString(PRIVATE_KEY, PUBLIC_KEY, password));
console.log(encrypted_data);
let decrypted_data: string = ArrayBufferToBase64(await decryptString(PRIVATE_KEY, PUBLIC_KEY, encrypted_data));
console.log(decrypted_data);
})();
I'm able to encrypt the string stored by the password
variable but not able to decrypt it.
Getting following error message :
PromiseĀ {<pending>}
VM98:33 FSWTBSgdQNVgYOelgRpU5JZm/4G0TRynIyr2jg==
VM98:36 Uncaught (in promise) Error
await (async)
(anonymous) @ VM98:36
Also, attaching screenshot for details not pasted under logs:
Need help to understand the root cause of this error alongwith the fix I can add to the script to successfully encrypt/decrypt some data.
Incase if someone's aware of a library that I can easily use on both client/server side, that would also be appreciated as an answer.
Below is the script that worked for me as per inputs provided from the community:
function base64ToArrayBuffer(base64: string): ArrayBuffer {
return Uint8Array.from(atob(base64), c => c.charCodeAt(0))
}
function ArrayBufferToBase64(buffer: ArrayBuffer): string {
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
}
async function encryptString(privateKey: string, publicKey: string, data: string) {
let params: AesGcmParams = {
name: 'AES-GCM',
iv: base64ToArrayBuffer(publicKey)
}
let keyImported: CryptoKey = await window.crypto.subtle.importKey("raw", base64ToArrayBuffer(privateKey), "AES-GCM", true, ["encrypt", "decrypt"]);
let dataInstance: ArrayBuffer = new TextEncoder().encode(data);
return window.crypto.subtle.encrypt(params, keyImported, dataInstance);
}
async function decryptString(privateKey: string, publicKey: string, data: string) {
let params: AesGcmParams = {
name: 'AES-GCM',
iv: base64ToArrayBuffer(publicKey)
}
let keyImported: CryptoKey = await window.crypto.subtle.importKey("raw", base64ToArrayBuffer(privateKey), "AES-GCM", true, ["encrypt", "decrypt"]);
return window.crypto.subtle.decrypt(params, keyImported, base64ToArrayBuffer(data));
}
// Key for encryption/decryption process
let PRIVATE_KEY: string = 'Fdz2z54vAwIjq0tginHyzgyL62q+f4sBlMi5IOOXJ7c=';
let PUBLIC_KEY: string = btoa(String.fromCharCode(...window.crypto.getRandomValues(new Uint8Array(12))));
// Password to be encrypted before being sent to server
let password: string = "Test@123";
(async () => {
let encrypted_data: string = ArrayBufferToBase64(await encryptString(PRIVATE_KEY, PUBLIC_KEY, password));
console.log(encrypted_data);
let decrypted_data: any = new TextDecoder().decode(await decryptString(PRIVATE_KEY, PUBLIC_KEY, encrypted_data));
console.log(decrypted_data);
})();