node.js Create initialization vector (IV) from random source - node.js

How to create a initialization vector (IV) from a random source in NodeJS, like I do in PHP as follows:
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
In NodeJS I thought crypto.createCipheriv could help but no.

You are on the right track and will still need to use:
Crypto.createCipheriv()
The function that can generate you a randomized initialization vector would be:
Crypto.randomBytes(16)
The 16 signifies the number of bytes required to fulfil the required length of the vector.
To give an example:
var iv = Crypto.randomBytes(16);
var cipher = Crypto.createCipheriv('aes-128-cbc', new Buffer(<128 bit password>), iv);
var encrypted = cipher.update(clearText);
var finalBuffer = Buffer.concat([encrypted, cipher.final()]);
//Need to retain IV for decryption, so this can be appended to the output with a separator (non-hex for this example)
var encryptedHex = iv.toString('hex') + ':' + finalBuffer.toString('hex')
For the sake of completeness, here's an example for decrypting the above encryptedHex
var encryptedArray = encryptedHex.split(':');
var iv = new Buffer(encryptedArray[0], 'hex');
var encrypted = new Buffer(encryptedArray[1], 'hex');
var decipher = Crypto.createDecipheriv('aes-128-cbc', new Buffer(<128 bit password>), iv);
var decrypted = decipher.update(encrypted);
var clearText = Buffer.concat([decrypted, decipher.final()]).toString();

Related

NodeJS AESCFB + pkcs7 padding decryption

I'm trying to port the following Go functions to nodeJS using crypt or crypt-js but i'm having issues trying to figure out what's wrong:
The Go encryption code is available at https://go.dev/play/p/O88Bslwd-qh ( both encrypt and decrypt work)
The current nodejs implementation is:
var decryptKey= "93D87FF936DAB334C2B3CC771C9DC833B517920683C63971AA36EBC3F2A83C24";
const crypto = require('crypto');
const algorithm = 'aes-256-cfb';
const BLOCK_SIZE = 16;
var message = "8a0f6b165236391ac081f5c614265b280f84df882fb6ee14dd8b0f7020962fdd"
function encryptText(keyStr, text) {
const hash = crypto.createHash('sha256');
//Decode hex key
keyStr = Buffer.from(keyStr, "hex")
hash.update(keyStr);
const keyBytes = hash.digest();
const iv = crypto.randomBytes(BLOCK_SIZE);
const cipher = crypto.createCipheriv(algorithm, keyBytes, iv);
cipher.setAutoPadding(true);
let enc = [iv, cipher.update(text,'latin1')];
enc.push(cipher.final());
return Buffer.concat(enc).toString('hex');
}
function decryptText(keyStr, text) {
const hash = crypto.createHash('sha256');
//Decode hex key
keyStr = Buffer.from(keyStr, "hex")
hash.update(keyStr);
const keyBytes = hash.digest();
const contents = Buffer.from(text, 'hex');
const iv = contents.slice(0, BLOCK_SIZE);
const textBytes = contents.slice(BLOCK_SIZE);
const decipher = crypto.createDecipheriv(algorithm, keyBytes, iv);
decipher.setAutoPadding(true);
let res = decipher.update(textBytes,'latin1');
res += decipher.final('latin1');
return res;
}
console.log(message)
result = decryptText(decryptKey,message);
console.log(result);
message = encryptText(decryptKey,'hola').toString();
console.log(message)
result = decryptText(decryptKey,message);
console.log(result);
Any idea why it is not working as expected?
Note: I know that padding is not required with cfb but i can't modify the encryption code, it just for reference.
I don't know Go or the specifics of aes.NewCipher(key), but from its documentation it doesn't look like it's hashing the key in any way. The Go code you're linking to also doesn't hash it, so I'm not sure why you're hashing it in the Node.js code.
This should be sufficient:
function encryptText(keyStr, text) {
const keyBytes = Buffer.from(keyStr, "hex")
…
}
function decryptText(keyStr, text) {
const keyBytes = Buffer.from(keyStr, 'hex');
…
}
As an aside: it looks like you may be encrypting JSON blocks with these functions. If so, I would suggest not using any encoding (like latin1) during the encryption/decryption process, given that JSON text must be encoded using UTF-8.

Decrypting AES-256-CBC Cipher using Node.js

I'm trying to decrypt an AES-256-CBC ciphertext from a web service using Node.js but can't seem to make it work and honestly lost.
const raw = `{
"iv":"uUwGJgxslfYiahji3+e2jA==",
"docMimeType":"text\/xml",
"doc":"1XLjWZlMMrgcpR6QtfwExQSOOPag1BJZTo1QEkcDrY6PFesWoVw8xrbHFsEYyMVDeemzk+5kBnb3\r\nqBmcUtkSFs7zDsxjYZkkEU9nyq1jXFz99fGylIealw37FPMaK0gviXESRO5AHMs46tpgSQcuWX0Z\r\nV7+mnTvjmaRHi4p1Cvg8aYfDO1aIWWWjAwOTCyopyCwribbGoEdiYDc5pERHpw=="
}`;
const cleanedEncryptedDataAsJsonStr = raw.replace(/\r?\n|\r/g, " ")
const data = JSON.parse(cleanedEncryptedDataAsJsonStr)
const ivBase64 = data.iv
const iv = Buffer.from(ivBase64, 'base64').toString('hex').substring(0, 16)
const plainKey = 'PHilheaLthDuMmyciPHerKeyS'
const hashKey = crypto.createHash('sha256');
hashKey.update(plainKey)
const key = hashKey.digest('hex').substring(0, 32)
let encrypted = Buffer.from(data.doc, 'base64').toString('hex')
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
let decrypted = decipher.update(encrypted, 'hex', 'utf-8')
decrypted += decipher.final('utf-8')
so I'm receiving a json object which contains the iv and the encrypted data(doc), and I have a copy of the cipher key which according to the docs needs to be hashed during the decryption.
doc: base64 encoding of the encrypted data
iv: base64 encoded value of initialization vector during encryption
When I run my code, the error is:
Error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
and also not sure on how to handle \r\n in the raw json string.
The decrypted message should be:
<eEMPLOYERS ASOF="07-02-2022"><employer pPEN="110474000002" pEmployerName="JOSE A TERAMOTO ODM" pEmployerAddress="ORANBO, PASIG CITY"/></eEMPLOYERS
There are the following issues in the code:
IV and key must not be hex encoded.
The default PKCS#7 padding must be disabled since Zero padding was applied during encryption (if desired, explicitly remove the trailing 0x00 padding bytes).
Fixed code:
var crypto = require('crypto')
const raw = `{
"iv":"uUwGJgxslfYiahji3+e2jA==",
"docMimeType":"text\/xml",
"doc":"1XLjWZlMMrgcpR6QtfwExQSOOPag1BJZTo1QEkcDrY6PFesWoVw8xrbHFsEYyMVDeemzk+5kBnb3\r\nqBmcUtkSFs7zDsxjYZkkEU9nyq1jXFz99fGylIealw37FPMaK0gviXESRO5AHMs46tpgSQcuWX0Z\r\nV7+mnTvjmaRHi4p1Cvg8aYfDO1aIWWWjAwOTCyopyCwribbGoEdiYDc5pERHpw=="
}`;
const cleanedEncryptedDataAsJsonStr = raw.replace(/\r?\n|\r/g, " ")
const data = JSON.parse(cleanedEncryptedDataAsJsonStr)
const ivBase64 = data.iv
const iv = Buffer.from(ivBase64, 'base64') // .toString('hex').substring(0, 16) // Fix 1: no hex encoding
const plainKey = 'PHilheaLthDuMmyciPHerKeyS'
const hashKey = crypto.createHash('sha256')
hashKey.update(plainKey)
const key = hashKey.digest() // .digest('hex').substring(0, 32) // Fix 2: no hex encoding
let encrypted = Buffer.from(data.doc, 'base64').toString('hex')
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
decipher.setAutoPadding(false) // Fix 3: disable default PKCS#7 padding
let decrypted = decipher.update(encrypted, 'hex', 'utf-8')
decrypted += decipher.final('utf-8')
console.log(decrypted) // plaintext zero-padded, if necessary remove trailing 0x00 values, e.g. as in the following:
console.log(decrypted.replace(new RegExp("\0+$"), "")) // <eEMPLOYERS ESOF="07-02-2022"><employer pPEN="110474000002" pEmployerName="JOSE A TERAMOTO ODM" pEmployerAddress="ORANBO, PASIG CITY"/></eEMPLOYERS>

Encrypting file in Node via Crypto and Stream

I want to read from a stream then encrypt it and finally write it to another file.
This is my code:
var fs = require('fs');
var crypto = require('crypto');
var infile = fs.createReadStream('a.dmg');
var outfile = fs.createWriteStream('b.dmg');
var encrypt = crypto.createCipher('aes192', 'behdad');
var size = fs.statSync('a.dmg').size;
console.log(size);
infile.on('data',function(data) {
var percentage = parseInt(infile.bytesRead) / parseInt(size);
console.log(percentage * 100);
var encrypted = encrypt.read(data);
console.log(encrypted);
if(encrypted){
console.log(encrypted);
outfile.write(encrypted);
}
});
infile.on('close', function() {
encrypt.end();
outfile.close();
});
But it returns an empty file, and encrypted is null. What is the problem? I don't want to use pipe .
You really want to use Cipher#update and Cipher#final instead of Stream#read, because the function signature is read([size]) and data is not a size.
var fs = require('fs');
var crypto = require('crypto');
var infile = fs.createReadStream('a.dmg');
var outfile = fs.createWriteStream('b.dmg');
var encrypt = crypto.createCipher('aes192', 'behdad');
var size = fs.statSync('a.dmg').size;
console.log(size);
infile.on('data',function(data) {
var percentage = parseInt(infile.bytesRead) / parseInt(size);
console.log(percentage * 100);
var encrypted = encrypt.update(data);
console.log(encrypted);
if(encrypted){
console.log(encrypted);
outfile.write(encrypted);
}
});
infile.on('close', function() {
outfile.write(encrypt.final());
outfile.close();
});
Since crypto.createCipher is deprecated now. You should use crypto.createCipheriv where you provide a key and IV. That means that you should stretch the password that you use with PBKDF2 or similar to get a key and generate a random IV to get semantic security. Since the salt for PBKDF2 and the IV are not supposed to be secret, they can be written in front of the ciphertext. Since they have always the same length (salt is usually 8-16 bytes and IV always 16 bytes for AES-CBC), you know how many bytes you have to read in order to get those values back. Keep in mind that the decryption code has to have proper error handling.

"Unsupported state or unable to authenticate data" with aes-128-gcm in Node

I'm trying to implement encrypt/decrypt functions using aes-128-gcm as provided by node crypto. From my understanding, gcm encrypts the ciphertext but also hashes it and provides this as an 'authentication tag'. However, I keep getting the error: "Unsupported state or unable to authenticate data".
I'm not sure if this is an error in my code - looking at the encrypted ciphertext and auth tag, the one being fetched by the decrypt function is the same as the one produced by the encrypt function.
function encrypt(plaintext) {
// IV is being generated for each encryption
var iv = crypto.randomBytes(12),
cipher = crypto.createCipheriv(aes,key,iv),
encryptedData = cipher.update(plaintext),
tag;
// Cipher.final has been called, so no more encryption/updates can take place
encryptedData += cipher.final();
// Auth tag must be generated after cipher.final()
tag = cipher.getAuthTag();
return encryptedData + "$$" + tag.toString('hex') + "$$" + iv.toString('hex');
}
function decrypt(ciphertext) {
var cipherSplit = ciphertext.split("$$"),
text = cipherSplit[0],
tag = Buffer.from(cipherSplit[1], 'hex'),
iv = Buffer.from(cipherSplit[2], 'hex'),
decipher = crypto.createDecipheriv(aes,key,iv);
decipher.setAuthTag(tag);
var decryptedData = decipher.update(text);
decryptedData += decipher.final();
}
The error is being thrown by decipher.final().
In case if someone still tries to get a working example of encryption and decryption process.
I've left some comments that should be taken into consideration.
import * as crypto from 'crypto';
const textToEncode = 'some secret text'; // utf-8
const algo = 'aes-128-gcm';
// Key bytes length depends on algorithm being used:
// 'aes-128-gcm' = 16 bytes
// 'aes-192-gcm' = 24 bytes
// 'aes-256-gcm' = 32 bytes
const key = crypto.randomBytes(16);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(algo, key, iv);
const encrypted = Buffer.concat([
cipher.update(Buffer.from(textToEncode, 'utf-8')),
cipher.final(),
]);
const authTag = cipher.getAuthTag();
console.info('Value encrypted', {
valueToEncrypt: textToEncode,
encryptedValue: encrypted.toString('hex'),
authTag: authTag.toString('hex'),
});
// It's important to use the same authTag and IV that were used during encoding
const decipher = crypto.createDecipheriv(algo, key, iv);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final(),
]);
console.info('Value decrypted', {
valueToDecrypt: encrypted.toString('hex'),
decryptedValue: decrypted.toString('utf-8'),
});
I managed to fix this: the issue was that I wasn't specifying an encoding type for cipher.final() and I was returning it within a String, so it wasn't returning a Buffer object, which decipher.final() was expecting.
To fix, I add 'utf-8' to 'hex' encoding parameters within my cipher.update and cipher.final, and vice versa in decipher.
Edited to add code example - note this is from 2018, so may be outdated now.
function encrypt(plaintext) {
// IV is being generated for each encryption
var iv = crypto.randomBytes(12),
cipher = crypto.createCipheriv(aes,key,iv),
encryptedData = cipher.update(plaintext, 'utf-8', 'hex'),
tag;
// Cipher.final has been called, so no more encryption/updates can take place
encryptedData += cipher.final('hex');
// Auth tag must be generated after cipher.final()
tag = cipher.getAuthTag();
return encryptedData + "$$" + tag.toString('hex') + "$$" + iv.toString('hex');
}
function decrypt(ciphertext) {
var cipherSplit = ciphertext.split("$$"),
text = cipherSplit[0],
tag = Buffer.from(cipherSplit[1], 'hex'),
iv = Buffer.from(cipherSplit[2], 'hex'),
decipher = crypto.createDecipheriv(aes, key, iv);
decipher.setAuthTag(tag);
var decryptedData = decipher.update(text, 'hex', 'utf-8');
decryptedData += decipher.final('utf-8');
}

How to decrypt cookie with nodejs

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!

Resources