Decrypting blowfish-ecb with nodejs crypto vs php's mcrypt - node.js

I'm trying to decode the following base64-encoded ciphertext in Node.js with the built-in crypto library
2tGiKhSjSQEjoDNukf5BpfvwmdjBtA9kS1EaNPupESqheZ1TCr5ckEdWUvd+e51XWLUzdhBFNOBRrUB5jR64Pjf1VKvQ4dhcDk3Fdu4hyUoBSWfY053Rfd3fqpgZVggoKk4wvmNiCuEMEHxV3rGNKeFzOvP/P3O5gOF7HZYa2dgezizXSgnnD6mCp37OJXqHuAngr0pps/i9819O6FyKgu6t2AzwbWZkP2sXvH3OGRU6oj5DFTgiKGv1GbrM8mIrC7rlRdNgiJ9dyHrOAwqO+SVwzhhTWj1K//PoyyzDKUuqqUQ6AvJl7d1o5sHNzeNgJxhywMT9F10+gnliBxIg8gGSmzBqrgwUNZxltT4uEKz67u9eJi59a0HBBi/2+umzwOCHNA4jl1x0mv0MhYiX/A==
It seems to work with PHP's mcrypt functions using the string typeconfig.sys^_- as the key, as shown by inputting the value into http://www.tools4noobs.com/online_tools/decrypt/ and selecting Blowfish, ECB, Base64 decode.
However, when I run the following code in Node.js:
var crypto = require('crypto');
var data = "2tGiKhSjSQEjoDNukf5BpfvwmdjBtA9kS1EaNPupESqheZ1TCr5ckEdWUvd+e51XWLUzdhBFNOBRrUB5jR64Pjf1VKvQ4dhcDk3Fdu4hyUoBSWfY053Rfd3fqpgZVggoKk4wvmNiCuEMEHxV3rGNKeFzOvP/P3O5gOF7HZYa2dgezizXSgnnD6mCp37OJXqHuAngr0pps/i9819O6FyKgu6t2AzwbWZkP2sXvH3OGRU6oj5DFTgiKGv1GbrM8mIrC7rlRdNgiJ9dyHrOAwqO+SVwzhhTWj1K//PoyyzDKUuqqUQ6AvJl7d1o5sHNzeNgJxhywMT9F10+gnliBxIg8gGSmzBqrgwUNZxltT4uEKz67u9eJi59a0HBBi/2+umzwOCHNA4jl1x0mv0MhYiX/A==";
var decipher = crypto.createDecipher('bf-ecb', 'typeconfig.sys^_-');
data = decipher.update(data, "base64", "utf8");
data += decipher.final("utf8");
console.log(data);
I get garbage output:
y
�:����d�(����Q�i��z1��4�� �k�(� ��a5����u��73c/��(ֻ��)��������fȠ���
�ec�-<z�8����(�-L���ԛ�I��1L*��u�4�j-�Чh쭊#\P)?޼�.�^���q㊬�U���W&�x��85�T-ג9,dE<g}�`*�
��|#����k"�!�D'u���,x��7����
��9q=q�q��ա>�w�T����H3͜�i)R��zy��C��
��o�
I've also tried a test of the library itself, in that it seems to be able to handle stuff it encodes itself fine:
var crypto = require('crypto')
var cipher = crypto.createCipher("bf-ecb", "key");
var data = cipher.update("foobar", "utf8", "base64");
data += cipher.final("base64");
console.log(data);
var decipher = crypto.createDecipher("bf-ecb", "key");
data = decipher.update(data, "base64", "utf8");
data += decipher.final("utf8");
console.log(data);
produces:
y0rq5pYkiU0=
foobar
but copy-and-pasting that base64 string and inputting it into http://www.tools4noobs.com/online_tools/decrypt/ alongside the key "key" produces garbage output also.
Shouldn't these two libraries produce the same output, or is there something I've done wrong?

Node.js computes the MD5 hash of the password before using it as the key. As far as I can tell, mcrypt uses the key as-is.
Compute the MD5 hash of the password, and use that as the mcrypt key.

https://github.com/tugrul/node-mcrypt
var mcrypt = require('mcrypt');
var bfEcb = new mcrypt.MCrypt('blowfish', 'ecb');
bfEcb.open('typeconfig.sys^_-');
var cipherText = new Buffer('2tGiKhSjSQEjoDNukf5BpfvwmdjBtA9kS1EaNPupESqheZ1TCr5ckEdWUvd+e51XWLUzdhBFNOBRrUB5jR64Pjf1VKvQ4dhcDk3Fdu4hyUoBSWfY053Rfd3fqpgZVggoKk4wvmNiCuEMEHxV3rGNKeFzOvP/P3O5gOF7HZYa2dgezizXSgnnD6mCp37OJXqHuAngr0pps/i9819O6FyKgu6t2AzwbWZkP2sXvH3OGRU6oj5DFTgiKGv1GbrM8mIrC7rlRdNgiJ9dyHrOAwqO+SVwzhhTWj1K//PoyyzDKUuqqUQ6AvJl7d1o5sHNzeNgJxhywMT9F10+gnliBxIg8gGSmzBqrgwUNZxltT4uEKz67u9eJi59a0HBBi/2+umzwOCHNA4jl1x0mv0MhYiX/A==', 'base64');
console.log(bfEcb.decrypt(cipherText).toString());
bfEcb.close();

Related

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>

How to decrypt a value in frontend which is encrypted in the backend (nodejs)?

Backend developers have encrypted a value in nodejs using crypto module. The code is shown below:
const _encrypt = async function(text){
var cipher = crypto.createCipher('aes-256-cbc','123|a123123123123123#&12')
var crypted = cipher.update(text,'utf8','hex')
crypted += cipher.final('hex');
console.log("in generic function....encrpted val", crypted)
return crypted;
}
I need to decrypt this value in the front end (Angular). So I tried decrypting like below:
let bytes = CryptoJS.AES.decrypt("e0912c26238f29604f5998fa1fbc78f6",'123|a123123123123123#&12');
if(bytes.toString()){
let m = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
console.log("data ",m);
}
using hardcoded value. But Im getting Error: Malformed UTF-8 data error. Can anybody please tell me how to decrypt this in angular side?
This is a tricky enough one.. the crypto.createCipher function creates a key and IV from the password you provide (See the createCipher documentation for details).
This is implemented using the OpenSSL function EVP_BytesToKey.
A JavaScript implementation is available here: openssl-file.. we'll use this to get a key and IV from the password.
So there are two steps here:
Get a key and IV from your password.
Use these with Crypto.js to decode your encoded string.
Step 1: Get key and IV (Run in Node.js )
const EVP_BytesToKey = require('openssl-file').EVP_BytesToKey;
const result = EVP_BytesToKey(
'123|a123123123123123#&12',
null,
32,
'MD5',
16
);
console.log('key:', result.key.toString('hex'));
console.log('iv:', result.iv.toString('hex'));
Step 2: Decrypt string:
const encryptedValues = ['e0912c26238f29604f5998fa1fbc78f6', '0888e0558c3bce328cd7cda17e045769'];
// The results of putting the password '123|a123123123123123#&12' through EVP_BytesToKey
const key = '18bcd0b950de300fb873788958fde988fec9b478a936a3061575b16f79977d5b';
const IV = '2e11075e7b38fa20e192bc7089ccf32b';
for(let encrypted of encryptedValues) {
const decrypted = CryptoJS.AES.decrypt({ ciphertext: CryptoJS.enc.Hex.parse(encrypted) }, CryptoJS.enc.Hex.parse(key), {
iv: CryptoJS.enc.Hex.parse(IV),
mode: CryptoJS.mode.CBC
});
console.log('Ciphertext:', encrypted);
console.log('Plain text:', decrypted.toString(CryptoJS.enc.Utf8));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js"></script>
Note that if you change the password you need to generate a new key and iv using EVP_BytesToKey.
I should note that createCipher is now deprecated, so use with caution. The same applies to EVP_BytesToKey.

NodeJS crypto fails to correctly decrypt the one that was encrypted by some other tools

I used https://encode-decode.com/aes-256-ctr-encrypt-online/ to encrypt the plain text and then use nodejs crypto aes-2556-ctr algorithm to decrypt but it doesn't return the original text.
plain text: test
Key: 12345678901234567890123456789012
encrypted text using https://encode-decode.com/aes-256-ctr-encrypt-online/: D/EU6g==
Following is the code that I used in nodejs:
var crypto = require('crypto'),
algorithm = 'aes-256-ctr',
key = '12345678901234567890123456789012';
function encrypt(text){
var cipher = crypto.createCipher(algorithm,key);
var crypted = cipher.update(text,'uft8', 'base64');
crypted += cipher.final('base64');
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipher(algorithm,key);
var dec = decipher.update(text, 'base64', 'utf8');
dec += decipher.final('utf8');
return dec;
}
I can also see that nodejs encrypt returns the different output that the tool I am using to encrypt.
Does anyone know what might be missing here?
Using createCipheriv worked for me. Thanks!

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'));

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