I am trying to encrypt a piece of string in nodejs and need to decrypt that in front end javascript. In nodejs I was using the crypto library and in front end using web crypto. Facing some error while decrypting in the front end.
NodeJS
const crypto = require('crypto');
const iv = crypto.randomBytes(12);
const algorithm = 'aes-256-gcm';
let password = 'passwordpasswordpasswordpassword';
let text = 'Hello World!';
let cipher = crypto.createCipheriv(algorithm, password, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
var tag = cipher.getAuthTag();
let cipherObj = {
content: encrypted,
tag: tag,
iv: iv
}
Front End
let cipherObj; //GET FROM BE
let aesKey = await crypto.subtle.importKey(
"raw",
Buffer.from('passwordpasswordpasswordpassword'), //password
"AES-GCM",
true,
["decrypt"]
);
let decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: Buffer.from(cipherObj.iv),
tagLength: 128
},
aesKey,
Buffer.concat([Buffer.from(cipherObj.content), Buffer.from(cipherObj.tag)])
);
Decrypt function in the front-end is throwing an error.
ERROR Error: Uncaught (in promise): OperationError
at resolvePromise (zone.js:814)
at zone.js:724
at rejected (main.js:231)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:388)
at Object.onInvoke (core.js:3820)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:387)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:138)
at zone.js:872
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
at Object.onInvokeTask (core.js:3811)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188)
at drainMicroTaskQueue (zone.js:595)
PS: I am using Angular 7 in front end
I was able to get this working with some changes:
Helpers - NodeJS and Browser
// look up tables
var to_hex_array = [];
var to_byte_map = {};
for (var ord=0; ord<=0xff; ord++) {
var s = ord.toString(16);
if (s.length < 2) {
s = "0" + s;
}
to_hex_array.push(s);
to_byte_map[s] = ord;
}
// converter using lookups
function bufferToHex2(buffer) {
var hex_array = [];
//(new Uint8Array(buffer)).forEach((v) => { hex_array.push(to_hex_array[v]) });
for (var i=0; i<buffer.length; i++) {
hex_array.push(to_hex_array[buffer[i]]);
}
return hex_array.join('')
}
// reverse conversion using lookups
function hexToBuffer(s) {
var length2 = s.length;
if ((length2 % 2) != 0) {
throw "hex string must have length a multiple of 2";
}
var length = length2 / 2;
var result = new Uint8Array(length);
for (var i=0; i<length; i++) {
var i2 = i * 2;
var b = s.substring(i2, i2 + 2);
result[i] = to_byte_map[b];
}
return result;
}
The backend uses hex2buffer and the frontend uses buffer2hex, but you can just include that code in both.
So the backend code is the above helpers plus:
NodeJS
const crypto = require('crypto');
const iv = crypto.randomBytes(12);
const algorithm = 'aes-256-gcm';
let password = 'This is my password';
let key = crypto.createHash("sha256").update(password).digest();
let text = 'This is my test string, with 🎉 emoji in it!';
let cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
var tag = cipher.getAuthTag();
let cipherObj = {
content: encrypted,
tag: bufferToHex2(tag),
iv: bufferToHex2(iv)
}
console.log(JSON.stringify(cipherObj));
The output changes every run due to random IV, but for example:
{"content":"22da4796365ac1466f40022dd4510266fa3e24900b816f365e308cf06c95237783d1043c7deeb45d00381f8ff9ed","tag":"b7007905163b2d4890c9452c8edc1821","iv":"eb4758787164f95ac22ee50d"}
So then the example frontend code is the above helper functions, plus:
Browser
let cipherObj; //GET FROM BACKEND
// For example:
cipherObj = {"content":"22da4796365ac1466f40022dd4510266fa3e24900b816f365e308cf06c95237783d1043c7deeb45d00381f8ff9ed","tag":"b7007905163b2d4890c9452c8edc1821","iv":"eb4758787164f95ac22ee50d"}
let password = 'This is my password';
let enc = new TextEncoder();
let key = await window.crypto.subtle.digest({ name:"SHA-256" }, enc.encode(password));
let aesKey = await crypto.subtle.importKey(
"raw",
key,
"AES-GCM",
true,
["decrypt"]
);
let decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: hexToBuffer(cipherObj.iv),
tagLength: 128
},
aesKey,
hexToBuffer(cipherObj.content + cipherObj.tag)
);
let dec = new TextDecoder();
console.log(dec.decode(decrypted));
// This is my test string, with 🎉 emoji in it!
Some crypto reference examples.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With