Node Crypto Encryption and decryption alogorithem - node.js

I am looking for standard utility for supporting the encryption & decryption based on the below algorithm in node server side.
algorithm: aes-256-gcm
using the createCipheriv, createDecipheriv from nodejs crypto module.
Please suggest some working references

The utility need to build it from your side, and based on your needs below are the small code may help you to build the utility:
const crypto = require('crypto');
// This two value (ivValueEn , ivValueDe) should be same, to decode the text properly!
// you can generate any strong value and past it at the same place of you can have it from the config value.
const ivValueEn = "c5949f09a7e67318888c5949f09a7e6c09ca51e602867318888c5949f09a7e6c09ca51e602867318888";
const ivValueDe = "c5949f09a7e6c09ca51e602867318888c5949f09a7e6c09ca51e602867319ca51e602867318888";
// insert your key here, better to chose a strong key
const keyValue = "c5949f09a7e6c09a7e6c09ca51e602867318888c5949f09a7e6c09ca51e602867318888";
// slicing are not required you can remove the slice(0,64) part & the console.log as well
const alog = 'aes-256-gcm'
const ivEn = ivValueEn.toString('hex').slice(0,64); console.log(ivEn);
const ivDe = ivValueDe.toString('hex').slice(0,64); console.log(ivDe);
// The one that working with me correctly is to slicing the key to (32) characters.
const key = keyValue.slice(0,32);
const cipher = crypto.createCipheriv(alog,key,ivEn);
const decipher = crypto.createDecipheriv (alog,key,ivDe);

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 properly encode strings so to decrypt with CryptoJs in NodeJS?

I am working out a custom hybrid encryption system. I've got symmetric encryption & asymmetric encryption & decryption all handled server-side. All I need to work out now is symmetric decryption.
I got some trouble because my client is sending symmetric key, iv & data all in string format (after asymmetric decryption), but CryptoJS is very touchy with it's encoding. It's also very confusing and vague as far as documentation goes- at least for a relatively new developer. I just can't figure out what encoding CryptoJS wants for each argument. I figure I should have guessed right by now, but no.
Docs
Some help I've gotten previously
I'm requesting help getting the encoding right so that I can decrypt with the following. And thanks a lot for any assistance.
Example of data after asymmetric decryption as per below (throw away keys):
symmetricKey: bDKJVr5wFtQZaPrs4ZoMkP2RjtaYpXo5HHKbzrNELs8=,
symmetricNonce: Z8q66bFkbEqQiVbrUrts+A==,
dataToReceive: "hX/BFO7b+6eYV1zt3+hu3o5g61PFB4V3myyU8tI3W7I="
exports.transportSecurityDecryption = async function mmTransportSecurityDecryption(dataToReceive, keys) {
const JSEncrypt = require('node-jsencrypt');
const CryptoJS = require("crypto-js");
// Asymmetrically decrypt symmetric cypher data with server private key
const privateKeyQuery = new Parse.Query("ServerPrivateKey");
const keyQueryResult = await privateKeyQuery.find({useMasterKey: true});
const object = keyQueryResult[0];
const serverPrivateKey = object.get("key");
const crypt = new JSEncrypt();
crypt.setPrivateKey(serverPrivateKey);
let decryptedDataString = crypt.decrypt(keys);
let decryptedData = JSON.parse(decryptedDataString);
// Symmetrically decrypt transit data
let symmetricKey = decryptedData.symmetricKey;
let symmetricNonce = decryptedData.symmetricNonce;
// Works perfectly till here <---
var decrypted = CryptoJS.AES.decrypt(
CryptoJS.enc.Hex.parse(dataToReceive),
CryptoJS.enc.Utf8.parse(symmetricKey),
{iv: CryptoJS.enc.Hex.parse(symmetricNonce)}
);
return decrypted.toString(CryptoJS.enc.Utf8);
}
You are using the wrong encoders for data, key and IV. All three are Base64 encoded (and not hex or Utf8). So apply the Base64 encoder.
The ciphertext must be passed to CryptoJS.AES.decrypt() as a CipherParams object or alternatively Base64 encoded, which is implicitly converted to a CipherParams object.
When both are fixed, the plain text is: "[\"001\",\"001\"]".
var symmetricKey = "bDKJVr5wFtQZaPrs4ZoMkP2RjtaYpXo5HHKbzrNELs8="
var symmetricNonce = "Z8q66bFkbEqQiVbrUrts+A=="
var dataToReceive = "hX/BFO7b+6eYV1zt3+hu3o5g61PFB4V3myyU8tI3W7I="
var decrypted = CryptoJS.AES.decrypt(
dataToReceive, // pass Base64 encoded
//{ciphertext: CryptoJS.enc.Base64.parse(dataToReceive)}, // pass as CipherParams object, works also
CryptoJS.enc.Base64.parse(symmetricKey),
{iv: CryptoJS.enc.Base64.parse(symmetricNonce)}
);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

Can the value from node crypto.createCipheriv('aes-256-gcm', ...).getAuthKey() be public?

I'm having trouble finding some information. Does anyone know if the value returned from cipher.getAuthTag() (--> returns MAC) can be publicly visible?
TL;DR
Can a message authentication code be publicly visible, or does this need to be kept secret like a password?
Some background, I am trying to encrypt a file. I found this stackoverflow question and answer that helped me get started. https://stackoverflow.com/a/27345933/11070228
After doing some research in the nodejs documentation, I found that the answer uses a deprecated function. createCipher. The new function to use should be createCipheriv.
So, to use the new createCipheriv, I used the documentation to write a new encryption and decryption function, similar to the one in the post using the new createCipheriv function. After writing the decryption function, I got an error that was
Error: Unsupported state or unable to authenticate data
After googling that issue, it led me here to this github post. In a nutshell, it said that the authTag generated with the cipher is needed to decrypt the file.
I did not know what this authTag was, and neither did anyone I knew. So I started googling that and it let me to this blogpost. It states
The authTag is the message authentication code (MAC) calculated during the encryption.
And here is a wikipedia article on what a message authentication code is.
So. Here is my question. Can a message authentication code be publicly visible, or does this need to be kept secret like a password?
My code, not as relevant, but might help someone create the encryption and decryption using createCipheriv and createDecipheriv.
Encryption
const crypto = require('crypto');
const fs = require('fs');
// const iv = crypto.randomBytes(32).toString('hex');
// EDIT - based on #President James K. Polk. The initialization vector should be 12 bytes long
// const iv = crypto.randomBytes(6).toString('hex');
// EDIT - based on #dsprenkels. I misunderstood #President James K. Polk
const iv = crypto.randomBytes(12).toString('hex');
const privateKey = 'private key that is 32 byte long';
const cipher = crypto.createCipheriv('aes-256-gcm', privateKey, iv);
const filename = 'somefile.txt';
const encFilename = 'somefile.txt.enc';
const unencryptedInput = fs.createReadStream(filename);
const encryptedOutput = fs.createWriteStream(encFilename);
unencryptedInput.pipe(cipher).pipe(encryptedOutput);
encryptedOutput.on('finish', () => {
const authTagAsHex = cipher.getAuthTag().toString('hex'); // <-- can this be public
console.log(authTagAsHex);
});
Decryption
const crypto = require('crypto');
const fs = require('fs');
// const publicIV = 'same iv generated during encryption crypto.randomBytes(32).toString("hex")';
// EDIT - based on #President James K. Polk. The initialization vector should be 12 bytes long
// const publicIV = 'same iv generated during encryption crypto.randomBytes(6).toString("hex")';
// EDIT - based on #dsprenkels. I misunderstood #President James K. Polk
const publicIV = 'same iv generated during encryption crypto.randomBytes(12).toString("hex")';
const authTag = 'same authKey generated from cipher.getAuthTag().toString("hex")';
const privateKey = 'private key that is 32 byte long';
const decipher = crypto.createDecipheriv('aes-256-gcm', privateKey, publicIV);
decipher.setAuthTag(Buffer.from(authTag, 'hex'));
const filename = 'somefile.txt';
const encFilename = 'somefile.txt.enc';
const readStream = fs.createReadStream(encFilename);
const writeStream = fs.createWriteStream(filename);
readStream.pipe(decipher).pipe(writeStream);
Yes. The MAC is considered public.
In general, message authentication codes are considered public. A message authentication code authenticates the (encrypted) message under the key that you provided. On other words, it is used by the receiver to check if the ciphertext did not change during transmission. In your situation, as long as the key remains secret, the attacker does not have any use for the MAC.
The MAC is normally put next to the ciphertext when the ciphertext is stored (just as the IV).
By the way, in your case you are were randomly generating the IV. That is fine, but beware that the amount of messages that can be safely encrypted under the same key is quite small. If an IV is used for multiple message (even once!) the complete security of this scheme breaks down. Really, you probably want this:
const iv = crypto.randomBytes(12);

jsSHA HMAC-512 value not match as Node.js HMAC

When using Node.js crypto module
const crypto = require('crypto');
HMACseed = crypto.createHmac('sha512', 'a55e3e55ff89d1cfeab1c85ac4dc7517d8d3228bb41a7d86de9cdf5587126de7').update('02de498327ba9544ba3b5c3d855a56a6761737a399d099b46b2a1d69491ca64ae400000001').digest('hex');
console.log(HMACseed)
result
08b87c15c5cc62ebcdb8cf5bf6a61cd168387fcc59db119e19ecd8deb67380dda98dd5faf7409face6ebcb187929176636f593dadbe7d7aa44a1ed59bbe0dff6
But using https://caligatio.github.io/jsSHA/
result
6b1312b3706844b11dd50012dd31be8d77f2f7cd9ec0624f730ee24bc4246084cbcaf10f63610cca1b4cc86e8b32a29b6c495a3b8bd28de4d3fd0b98df483530
key = 'a55e3e55ff89d1cfeab1c85ac4dc7517d8d3228bb41a7d86de9cdf5587126de7'
data = '02de498327ba9544ba3b5c3d855a56a6761737a399d099b46b2a1d69491ca64ae400000001'
I wonder why the jsSHA will result different value of HMAC-256.
You need to using hex as input or it will regard it as text input.
HMACseed = crypto.createHmac('sha512', Buffer.from('a55e3e55ff89d1cfeab1c85ac4dc7517d8d3228bb41a7d86de9cdf5587126de7', 'hex')).update(Buffer.from('02de498327ba9544ba3b5c3d855a56a6761737a399d099b46b2a1d69491ca64ae400000001','hex')).digest('hex');
console.log(HMACseed)

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).

Resources