How to decrypt cookie with nodejs - node.js

I am trying to make run this
function hex2a(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
//Raw cookie
var cookie = "B417B464CA63FE780584563D2DA4709B03F6195189044C26A29770F3203881DD90B1428139088D945CF6807CA408F201DABBADD59CE1D740F853A894692273F1CA83EC3F26493744E3D25D720374E03393F71E21BE2D96B6110CB7AC12E44447FFBD810D3D57FBACA8DF5249EB503C3DFD255692409F084650EFED205388DD8C08BF7B941E1AC1B3B70B9A8E09118D756BEAFF25834E72357FD40E80E76458091224FAE8";
//decryptionKey from issuers <machineKey>
var deckey = "FFA87B82D4A1BEAA15C06F6434A7EB2251976A838784E134900E6629B9F954B7";
var crypto = require('crypto');
var ivc = cookie, iv, cipherText, ivSize = 16, res = "";
ivc = new Buffer(ivc, 'hex');
iv = new Buffer(ivSize);
cipherText = new Buffer(ivc.length - ivSize);
ivc.copy(iv, 0, 0, ivSize);
ivc.copy(cipherText, 0, ivSize);
c = crypto.createDecipheriv('aes-256-cbc', hex2a(deckey), iv.toString('binary'));
res = c.update(cipherText, "binary", "utf8");
res += c.final('utf8');
console.log(res);
In this Q&A, it mentions about differences about node js versions, I tried that apply that one but with out success:
res = c.update(cipherText, "binary", "utf8");
line result such result
�sJ舸=�X7D������G����}x���T
and
res += c.final('utf8');
gives this error
0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
nodejs version: 4.1.2 and crypto version 0.0.3
How can I properly decrypt cookie with this algorith or can you suggest any other?

[Assuming you are trying to decrypt a .NET framework cookie]:
(Note: This answer was completely rewritten as things were not as simple as it seemed)
The encryption schema is described here, citing interesting parts:
VERIFY + DECRYPT DATA (fEncrypt = false, signData = true)
Input: buf represents ciphertext to decrypt, modifier represents data to be removed from the end of the plaintext (since it's not really plaintext data)
Input (buf): E(iv + m + modifier) + HMAC(E(iv + m + modifier))
Output: m
The 'iv' in the above descriptions isn't an actual IV. Rather, if ivType = > IVType.Random, we'll prepend random bytes ('iv') to the plaintext before feeding it to the crypto algorithms. Introducing randomness early in the algorithm prevents users from inspecting two ciphertexts to see if the plaintexts are related. If ivType = IVType.None, then 'iv' is simply an empty string. If ivType = IVType.Hash, we use a non-keyed hash of the plaintext.
The 'modifier' in the above descriptions is a piece of metadata that should be encrypted along with the plaintext but which isn't actually part of the plaintext itself. It can be used for storing things like the user name for whom this plaintext was generated, the page that generated the plaintext, etc. On decryption, the modifier parameter is compared against the modifier stored in the crypto stream, and it is stripped from the message before the plaintext is returned.
Which is (hopefully) implemented with the following script:
// Input
var cookie = "B417B464CA63FE780584563D2DA4709B03F6195189044C26A29770F3203881DD90B1428139088D945CF6807CA408F201DABBADD59CE1D740F853A894692273F1CA83EC3F26493744E3D25D720374E03393F71E21BE2D96B6110CB7AC12E44447FFBD810D3D57FBACA8DF5249EB503C3DFD255692409F084650EFED205388DD8C08BF7B941E1AC1B3B70B9A8E09118D756BEAFF25834E72357FD40E80E76458091224FAE8";
var decryptionKey = "FFA87B82D4A1BEAA15C06F6434A7EB2251976A838784E134900E6629B9F954B7";
var validationKey = "A5326FFC9D3B74527AECE124D0B7BE5D85D58AFB12AAB3D76319B27EE57608A5A7BCAB5E34C7F1305ECE5AC78DB1FFEC0A9435C316884AB4C83D2008B533CFD9";
// Parameters
var hmacSize=20
// Make buffers for input
var cookieBuffer = new Buffer(cookie, 'hex');
var decryptionKeyBuffer = new Buffer(decryptionKey, 'hex');
var validationKeyBuffer = new Buffer(validationKey, 'hex');
// Parse cookie
var curOffset=0;
var cipherText = new Buffer(cookieBuffer.length - hmacSize);
curOffset+=cookieBuffer.copy(cipherText, 0, curOffset, curOffset+cipherText.length);
var hmac = new Buffer(hmacSize);
curOffset+=cookieBuffer.copy(hmac, 0, curOffset, curOffset+hmac.length);
// Verify HMAC
var crypto = require('crypto');
var h = crypto.createHmac('sha1', validationKeyBuffer);
h.update(cipherText);
var expectedHmac = h.digest();
console.log('Expected HMAC: ' + expectedHmac.toString('hex'));
console.log('Actual HMAC: ' + hmac.toString('hex'));
//if(!expectedHmac.equals(hmac)) { // Note: Requires nodejs v0.11.13
// throw 'Cookie integrity error';
//}
// Decrypt
var zeroIv = new Buffer("00000000000000000000000000000000", 'hex');
var c = crypto.createDecipheriv('aes-256-cbc', decryptionKeyBuffer, zeroIv);
var plaintext = Buffer.concat([c.update(cipherText), c.final()]);
// Strip IV (which is the same length as decryption key -- see notes below)
var res = new Buffer(plaintext.length-decryptionKeyBuffer.length);
plaintext.copy(res, 0, decryptionKeyBuffer.length, plaintext.length);
// Output
console.log('HEX: ' + res.toString('hex'));
console.log('UTF-8: ' + res.toString('utf8'));
Giving result:
Expected HMAC: 88e332b9a27b8f6f8d805ae718c562c1c8b721ed
Actual HMAC: 6beaff25834e72357fd40e80e76458091224fae8
HEX: 010112ea9a47b2f2ce08fe121e7d78b6f2ce0801085400650073007400550073006500720016540065007300740020007400650073007400730073006f006e002c00200072006f006c0066007a006f007200012f00ff1d892908d9c497bd804f5f22eab043ff6368702c
UTF-8: ��G���}x�TestUserTest testsson, rolfzor/���ė��O_"��C�chp,
Some (random) notes about this code:
it assumes that AES is used for encryption and HMAC-SHA1 is used for authentication
as the used authentication key is not known, the integrity check condition is commented out and verification key from this very related question is used (which is the reason for authentication tag mismatch)
the padding used for AES encryption is PKCS#7
the 'modifier' field is assumed empty. If this is not the case you would have to check it and remove it from the plaintext
for production environment you definitely should check the authentication tag (otherwise you would expose yourself to nasty attacks)
to avoid even nastier attacks, the authentication tag should be tested for equality in constant time (which might be tricky to implement in nodejs). Please note that the commented-out code is very probably vulnerable to timing-attacks.
the IV length is equal to the key length (see here for the reason)
Disclaimer: I did not study the original .NET code thoroughly, nor am I a crypto expert so please do validate my thoughts
Good luck!

Related

PBEWithHmacSHA256AndAES_128 encryption in nodejs

I'm trying to encrypt a string using PBEWithHmacSHA256AndAES_128 in nodejs, however I'm having a bit of trouble determining the correct way to do it.
Lots of documentation state I can use the crypto library, which when I try crypto.getCiphers() I see 'aes-128-cbc-hmac-sha256' is supported.
I've tried various tutorials, https://www.geeksforgeeks.org/node-js-crypto-createcipheriv-method/ and such but I'm mainly hitting "Invalid key length" or "Invalid initialization vector" when I try to change the cipher type.
Could anyone point me to some documentation or code samples that may assist in achieving this?
PBEWithHmacSHA256AndAES_128 and aes-128-cbc-hmac-sha256 refer to different things.
Both encrypt with AES-128 in CBC mode, but the former uses a key derivation, the latter a MAC (more precisely an HMAC) for authentication.
Regarding NodeJS, the latter has apparently never worked reliably. In some versions exceptions are generated, in others no authentication is performed (i.e. the processing is functionally identical to AES-128-CBC), see here. This is not surprising since OpenSSL only intends this to be used in the context of TLS, see here, which of course also applies to NodeJS as this is just an OpenSSL wrapper.
But since you are concerned with PBEWithHmacSHA256AndAES_128, the aes-128-cbc-hmac-sha256 issues are in the end not relevant. PBEWithHmacSHA256AndAES_128 uses PBKDF2 (HMAC/SHA256) as key derivation, which is supported by NodeJS. A possible implementation that is functionally identical to PBEWithHmacSHA256AndAES_128 is:
var crypto = require("crypto");
// Key derivation
var password = 'my passphrase';
var salt = crypto.randomBytes(16); // some random salt
var digest = 'sha256';
var length = 16;
var iterations = 10000;
var key = crypto.pbkdf2Sync(password, salt, iterations, length, digest);
// Encryption
var iv = crypto.randomBytes(16); // some random iv
var cipher = crypto.createCipheriv('AES-128-CBC', key, iv);
var encrypted = Buffer.concat([cipher.update('The quick brown fox jumps over the lazy dog', 'utf8'), cipher.final()]);
// Output
console.log(salt.toString('base64')); // d/Gg4rn0Gp3vG6kOhzbAgw==
console.log(iv.toString('base64')); // x7wfJAveb6hLdO4xqgWGKw==
console.log(encrypted.toString('base64')); // RbN0MsUxCOWgBYatSbh+OIWJi8Q4BuvaYi6zMxqERvTzGtkmD2O4cmc0uMsuq9Tf
The encryption with PBEWithHmacSHA256AndAES_128 gives the same ciphertext when applying the same parameters. This can be checked e.g. with Java and the SunJCE provider which supports PBEWithHmacSHA256AndAES_128 (here).
Edit:
From the linked Java code for decryption all important parameters can be extracted directly:
var crypto = require("crypto");
// Input parameter (from the Java code for decryption)
var password = 'azerty34';
var salt = '12345678';
var digest = 'sha256';
var length = 16;
var iterations = 20;
var iv = password.padEnd(16, '\0').substr(0, 16);
var plaintext = '"My53cr3t"';
// Key derivation
var key = crypto.pbkdf2Sync(password, salt, iterations, length, digest);
// Encryption
var cipher = crypto.createCipheriv('AES-128-CBC', key, iv);
var encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
// Output
console.log(encrypted.toString('base64')); // bEimOZ7qSoAd1NvoTNypIA==
Note that the IV is equal to the password, but truncated if it is larger than 16 bytes or padded with 0x00 values at the end if it is shorter than 16 bytes (as is the case here).
The NodeJS code now returns the required ciphertext for the given input parameters.
Keep in mind that the static salt is a serious security risk.

How to decrypt a .NET Forms Authentication Cookie in Node.js

I am trying to see if its possible to decrypt a .NET Forms Authentication Cookie in Node.js
The cookie is generated via .NET Framework 4.6
If i check the machineKey value - it uses settings:
decryptionKey="xxxxxxxxxxxxxxxx" validation="SHA1" decryption="DES"
And if i look at the .NET source i believe it defaults to the cbc des cipher:
https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/mscorlib/system/security/cryptography/descryptoserviceprovider.cs#L90
With IV key length of 8 bytes
and hmac size of 20 bytes
In the case of the ASPX cookie it has format:
IV + DATA + HMAC
So if i try something like this below (which when decrypted should be a binary FormsAuthenticationTicket):
const COOKIE_CONTENTS = '86F1EDAAE112A4E56EB1DAA75411F07E8D82F648A87F13E8386735610....REDACTED FULL VALUE';
const decryptionKey = "xxxxxxxxxxxxxxxx";
const algorithm = 'des-cbc';
const key = Buffer.from(decryptionKey, "hex");
let cookie = COOKIE_CONTENTS;
let blob = Buffer.from(cookie, 'hex');
const ivSize = 8;
const hmacSize = 20;
let iv = blob.slice(0, ivSize);
let hmac = blob.slice(blob.length - hmacSize);
let encrypted = blob.slice(ivSize, blob.length - hmacSize);
console.log("Len (cookie):", cookie.length);
console.log("Len (blob):", blob.length);
console.log("IV:", iv, "len:", iv.length);
console.log("HMAC:", hmac, "len:", hmac.length);
console.log("Encrypted:", encrypted, "len:", encrypted.length);
const decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypted = Buffer.from(decipher.update(encrypted, 'binary', 'binary') + decipher.final('binary'), 'binary');
And it works!
However when the token is generated with httpRuntime setting:
<httpRuntime targetFramework="4.5" enableVersionHeader="false" maxRequestLength="10240" />
I get a failure
error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
And unfortunately this is the auth tokens i need to decrypt as they are on staging and production systems.
This may be due to the way .NET has changed the way auth is done when you opt into targetFramework="4.5"
It has .NET 4.5 “cryptographic improvements”
See: https://devblogs.microsoft.com/dotnet/cryptographic-improvements-in-asp-net-4-5-pt-2/
I think the rundown is that:
“Purpose” is passed to the crypto routines that describe purpose, we need to provide the same string to decrypt it
Changes to how Message Authentication Code is stored (MAC)
Some of the .NET source code is referenced here: https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/System.Web/Security/Cryptography/Purpose.cs
and also I can see the 4.5 setting block here: https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/System.Web/Security/FormsAuthentication.cs#L155
and the code eventually ends up here:
https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/System.Web/Security/Cryptography/MachineKeyDataProtectorFactory.cs#L25-L29
but its hard to follow after that. Seems as though 4.5 mode bipasses the DES settings we have passed to it, so who knows what cipher its actually using when in this mode?
Unfortunately I cant just remove the targetFramework="4.5" part as this will mean all user tokens will fail after this is rolled out, meaning all users will need to login again which is not acceptable.
Does anyone know more details on how this can be done with a 4.5 “cryptographic improvements” token?
Any ideas on what I am missing with these crypto settings - it would be great if this can be done in Node.js
UPDATE:
I have tried what #Sebastian has mentioned and tried to port over some of the python code without success (I believe i have written the node code correctly, please let me know if i'm missing something)
eg:
function writeUnsignedInt(v, buf, offset) {
buf.writeInt32BE(v, offset);
}
// conversion of this: https://lowleveldesign.org/2014/11/11/decrypting-asp-net-identity-cookies/
function deriveKey(key, label, context, keyLengthInBits) {
let labelCount = 0;
let contextCount = 0;
if (label) {
labelCount = label.length;
}
if (context) {
contextCount = context.length;
}
const buffer = Buffer.alloc((4 + labelCount + 1 + contextCount + 4));
if (labelCount > 0) {
buffer.write(label, 4, 'ascii');
}
if (contextCount > 0) {
buffer.write(context, 5 + labelCount, 'ascii');
}
writeUnsignedInt(keyLengthInBits, buffer, 5 + labelCount + contextCount);
let destOffset = 0;
let value = parseInt(keyLengthInBits / 8, 10);
let resultBuffer = Buffer.alloc(value);
let num = 1;
while(value > 0) {
writeUnsignedInt(num, buffer, 0);
var hmac = crypto.createHmac('sha512', key);
let bufferString = buffer.toString();
let hashedData = hmac.update(bufferString);
let generatedHmac = hashedData.digest('hex');
let count = Math.min(value, generatedHmac.length);
resultBuffer.write(generatedHmac.substring(0, count), destOffset);
destOffset += count;
value -= count;
num += 1;
}
return resultBuffer.toString();
}
const algorithm = 'des-cbc';
let key = Buffer.from(decryptionKey, "hex");
let blob = Buffer.from(cookie, 'hex');
const ivSize = 8;
const hmacSize = 20;
let iv = blob.slice(0, ivSize);
let hmac = blob.slice(blob.length - hmacSize);
let encrypted = blob.slice(ivSize, blob.length - hmacSize);
let dkey = deriveKey(key, 'FormsAuthentication.Ticket', '>Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware\x11ApplicationCookie\x02v1', 64);
const decipher = crypto.createDecipheriv(algorithm, dkey, iv);
let decrypted = Buffer.from(decipher.update(encrypted, 'binary', 'binary') + decipher.final('binary'), 'binary');
I think the main differences is:
I need to deal with DES and not AES (as thats what has been set in the Web.Config file) with different IV and key sizes.
Im dealing with a forms authentication ticket and not owin auth or anti forgery token.
Im taking a stab at what the "label" is, im setting it as ">Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware\x11ApplicationCookie\x02v1" but not sure thats correct (cant find the source code for this)
Im not sure I need to do any padding / base64 decoding of the encrypted data, because its in hex format (i basically get the DATA part from IV + DATA + HMAC and set it as a blob and pass to decipher.update)

NodeJS | Crypto Encryption is not producing correct results

I have a tricky problem to resolve. Not sure how to explain it correctly but will try my best. So here is what I am trying to do: I am trying to use a 3rd Party API, which wants me to encrypt a value and submits it. I successfully achieved it through C# code using the following block:
public string Encrypt(byte[] dataToEncrypt, byte[] keyBytes)
{
AesManaged tdes = new AesManaged();
tdes.KeySize = 256;
tdes.BlockSize = 128;
tdes.Key = keyBytes;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform crypt = tdes.CreateEncryptor();
byte[] cipher = crypt.TransformFinalBlock(dataToEncrypt, 0, dataToEncrypt.Length);
tdes.Clear();
return Convert.ToBase64String(cipher, 0, cipher.Length);
}
Now, I am trying to achieve the same in Node. I wrote the following function.
encrypt(buffer){
var buffbytes = new Buffer('my app key goes here to be used as password','utf8'); //converts the app key into buffer stream
return this.encrypt_key(new Buffer(buffer,'utf8'), buffbytes);
},
encrypt_key(buffer, keybytes){
var cipher = crypto.createCipher('aes-128-ecb',keybytes);
var crypted = cipher.update(buffer,'utf8','base64');
crypted = crypted+ cipher.final('base64');
return crypted;
},
This encryption code works fine. It encrypts it fine, but it doesn't encrypt it similar to what c# code does. When I take the encrypted text from C# code, and inject the encrypted result into the API call, it passes through fine, but when I use my encrypted result into the API call, it fails mentioning that the format of my key is incorrect.
I would like to know if these code blocks are same or not. I assume it is same, because both code using 128 bit AES, ECB Cipher and default padding for Crypto Node module is PKCS5 which is same as PKCS7 for 128 bit encryption. Please Help!
Edit: 9/19/2017
Fixed as per #smarx solution:
encrypt(buffer){
var buffbytes = new Buffer(helper.Constants.AppKey,'utf8'); //converts the app key into buffer stream
return this.encrypt_key(new Buffer(buffer,'utf8'), helper.Constants.AppKey);
},
encrypt_key(buffer, key){
var cipher = crypto.createCipheriv('aes-256-ecb',key,new Buffer(0));
var crypted = cipher.update(buffer,'utf8','base64');
crypted = crypted+ cipher.final('base64');
console.log('printed: ', crypted);
return crypted;
},
In your Node.js code, you're using the wrong cipher algorithm. Use aes-256-ecb, since you're using a 256-bit key. Also, be sure to use createCipheriv, since createCipher expects a password from which it derives an encryption key.
One-line fix:
const cipher = crypto.createCipheriv('aes-256-ecb', key, new Buffer(0));
The below two programs produce identical output (Q9VZ73VKhW8ZvdcBzm05mw==).
C#:
var key = System.Text.Encoding.UTF8.GetBytes("abcdefghijklmnopqrstuvwxyz123456");
var data = System.Text.Encoding.UTF8.GetBytes("Hello, World!");
var aes = new AesManaged {
Key = key,
Mode = CipherMode.ECB,
};
Console.WriteLine(Convert.ToBase64String(
aes.CreateEncryptor().TransformFinalBlock(data, 0, data.Length)));
Node.js:
const crypto = require('crypto');
const key = 'abcdefghijklmnopqrstuvwxyz123456';
const data = 'Hello, World!';
const cipher = crypto.createCipheriv('aes-256-ecb', key, new Buffer(0));
console.log(cipher.update(data, 'utf-8', 'base64') + cipher.final('base64'));

Decryption returns empty result - nodejs crypto

function encrypt() {
const iv = '3af545da025d5b07319cd9b2571670ca'
, payload = '01000000000000000000000000000000'
, key = 'c1602e4b57602e48d9a3ffc1b578d9a3';
const cipher = crypto.createCipheriv('aes128', new Buffer(key, 'hex'), new Buffer(iv, 'hex'));
const encryptedPayload = cipher.update(new Buffer(payload, 'hex'));
let encryptedPayloadHex = encryptedPayload.toString('hex');
console.log(encryptedPayloadHex); // returns 'ae47475617f38b4731e8096afa5a59b0'
};
function decrypt() {
const iv = '3af545da025d5b07319cd9b2571670ca'
, key = 'c1602e4b57602e48d9a3ffc1b578d9a3'
, payload = 'ae47475617f38b4731e8096afa5a59b0';
const decipher = crypto.createDecipheriv('aes128', new Buffer(key, 'hex'), new Buffer(iv, 'hex'));
const decryptedPayload = decipher.update(new Buffer(payload, 'hex'), 'hex', 'hex');
console.log(decryptedPayload); // returns empty string
// decipher.update(new Buffer(payload, 'hex')) // returns empty buffer
const decryptedPayloadHex = decipher.final('hex'); // returns 'EVP_DecryptFinal_ex:bad decrypt' error
// console.log(decryptedPayloadHex);
};
The decryption result, though, is always empty.
The nodejs docs state that update returns the value as string in given encoding, if provided, otherwise as Buffer. Nevertheless I tried using final as well, but no success.
P.S. In fact, I receive the encryptedPayload value and the iv from external source (they're not encrypted and generated by me), but I decided to test out the encryption (I have the plain payload value) and my encryption returns the same result as the one that I'm receiving externally.
Ok, so the problem turned out to be the padding. I got inspiration from here. I simply added
decipher.setAutoPadding(false);
right after I crete the decipher object.
That is weird though, because padding problems could occur when encryption is done in one language and decryption in another, but should not happen when encryption and decryption are done in the same language (as I did my testing here)... If anyone has comments on the padding issue - please add them, so that future viewers can gain knowledge (as well as me).

Nodejs decrypt using crypto error wrong final block length

I use this code to crypt/decrypt string value
var crypto = require('crypto');
function encrypt(text){
var cipher = crypto.createCipher('aes-256-cbc','secret key');
var encrypted = cipher.update(text.toString(),'utf8','hex') + cipher.final('hex');
return encrypted;
}
function decrypt(text){
var decipher = crypto.createDecipher('aes-256-cbc','secret key');
var decrypted = decipher.update(text.toString(),'hex','utf8') + decipher.final('utf8');
return decrypted ;
}
module.exports.encrypt = encrypt;
module.exports.decrypt = decrypt;
When i try to decrypt something that isn't crypted for example decrypt('test') it throw me the following error :
crypto.js:292
var ret = this._binding.final();
^
TypeError: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
at Decipher.Cipher.final (crypto.js:292:27)
I tryed also to use buffers without sucess and couldn't find any solution over Internet.
The real problem is I use this to decrypt cookie value. If a hacker creates a fake cookie with the value "test" it will crash my program.
The output of AES-CBC (without ciphertext stealing) is always a multiple of 16 bytes (32 hex characters). As you do not provide hexadecimal characters at all ("test") and since the string is not a multiple of 32 hexadecimal characters you will always see an error.
So this:
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
would for instance be valid.
So you need to check that what you get is containing the right characters and is of the right length. To make sure that you don't get any padding or content related errors you will need to put a (hexadecimal encoded) HMAC value calculated over the ciphertext at the end. Then first check encoding, length and then the HMAC. If the HMAC is correct you can be assured that the plaintext won't contain any invalid information after decryption.
I also faced the same issue. I had to go through all the comments to check for answer and #Alexey Ten's comment helped me. So in order to make #Alexey Ten's answer more visible below are the changes.
var crypto = require('crypto');
function encrypt(text){
try{
var cipher = crypto.createCipher('aes-256-cbc','secret key');
var encrypted = cipher.update(text.toString(),'utf8','hex') + cipher.final('hex');
return encrypted;
} catch(exception) {
throw exception;
}
}
function decrypt(text){
try{
var decipher = crypto.createDecipher('aes-256-cbc','secret key');
var decrypted = decipher.update(text.toString(),'hex','utf8') + decipher.final('utf8');
return decrypted ;
} catch(exception) {
throw exception;
}
}

Resources