5

Trying to recreate the following php encryption code in node.js:

$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($size, MCRYPT_RAND);
$msg = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, 'MY_KEY_LONG_STRING', 'PLAINTEXT', MCRYPT_MODE_ECB, $iv));

Tried this:

var text = 'PLAINTEXT';
var len = text.length;
for (var i = 0; i < 16 - len % 16; i++) {  // pad to multiple of block size 
    text += '\0';
}
var key = 'MY_KEY_LONG_STRING';
key = key.substr(0, 16); // trim to expected key size for aes128

var cipher = crypto.createCipher('aes-128-ecb', key);
cipher.setAutoPadding(false); // did our own padding, to match mcrypt_encrypt
var encrypted = cipher.update(text, 'utf8', 'base64');
encrypted += cipher.final('base64');

Getting different result from the php one...

Also tried creating cipher with IV (which shouldn't even be used in aes-128-ecb):

var cipher = crypto.createCipheriv('aes-128-ecb', key, '');

Also, different result from php. Any ideas how to make this behave exactly like the php version?

Pasha Bitz
  • 787
  • 6
  • 9
  • ECB cannot use an IV, so your php code can be condensed: `$msg = base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, 'MY_KEY_LONG_STRING', 'PLAINTEXT', MCRYPT_MODE_ECB));` – Artjom B. Jan 02 '15 at 19:51

3 Answers3

5

Playing two rather ill constructed libraries against each other can be fun. Rather than doing all the work, I'll help you along with ideas as requested:

  • rather than removing key bytes, PHP expands them using zero padding to the next available key size - that would be 192 bits or 24 bytes in your situation; to do this you need to specify aes-192-ecb as algorithm (you need to keep using MCRYPT_RIJNDAEL_128 but substitute, that 128 in PHP is the block size not the key size)
  • the padding is not correct, PHP pads 0..15 zero bytes instead of 1..16 bytes
  • you cannot use the 2 argument createCipher as the second argument is the password; node.js performs key derivation if you use that one, so you need to use the three argument createCipher instead and supply any 16 byte IV

The IV code in PHP only taxes the random number generator needlessly, the IV is not used.


Code to do the padding

var padSize = 16 - ((len + 16 - 1) % 16 + 1);
for (var i = 0; i < padSize; i++) {  // pad 0 .. 15 until to multiple of block size 
    text += '\0';
}

Or you could use your own padding method (in the question) unless len % 16 == 0.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Default disclaimer: ECB is not secure, transporting data without a MAC is even less secure (and I have serious doubts about using JavaScript and PHP for encryption as well) – Maarten Bodewes Dec 31 '14 at 09:48
  • Hi Maarten, thanks for the ideas: 1) when padding key to 24 bytes and calling `crypto.createCipheriv`, node.js gives the error `node-crypto : Invalid key length 24`. The fact that php expands to 24 bytes is helpful, though. 2) What do you mean by "PHP pads 0..15 ..." ? 3) The IV code in PHP, judging based on comments, is there to avoid an error that used to be thrown when not supplying the IV argument, in an earlier version of PHP. – Pasha Bitz Dec 31 '14 at 18:47
  • @PashaBitz 1) check my altered answer (on the first item) for a solution for the 24 byte key problem. 2) PHP pads with zero's until the next block size is reached, but it doesn't pad at allif the size is already x times the block size. 3) As for the IV, I would just give it a byte array of 16 bytes initialized to all zero's. You may need to provide an IV if it's not used, but it doesn't make sense to randomize it. – Maarten Bodewes Jan 01 '15 at 17:03
  • with the key padded to 24 characters, `crypto.createCipheriv` still throws the error `node-crypto : Invalid key length 24`. With the key trimmed to 16 characters, though, does produce identical results, thanks for spotting the padding bug!! – Pasha Bitz Jan 04 '15 at 01:47
  • Replacing with aes-192-ecb does produce an output identical to PHP, with the 24 byte key... Somehow I missed that comment in your original post. Thank you!! – Pasha Bitz Jan 04 '15 at 02:11
  • @MaartenBodewes why your calculations for `padSize` are so complicated? Are there any values which I can't think of that would break padding if we do it for `var padSize = len % 16`? – nass Jul 26 '19 at 15:50
  • That one may indeed be unnecessary complicated. Then again, yours is just plain wrong, you probably meant `var padSize = 16 - len % 16` :P – Maarten Bodewes Jul 26 '19 at 22:28
  • @MaartenBodewes oh ye, I've re-written it wrong from my own code. Sorry. `var padSize = 16 - len % 16` seems to be working well. – nass Jul 29 '19 at 10:14
3

Here is my code to solve problem with migration from PHP to NodeJS. I have strict 32 bytes key so I have to use aes-256-ecb.

PHP code I want rewrite:

$text = "Some super mega text I want to encode";
$skey = "rcbTw667C7zxghZ5U3gwhQlp22t8c5Rq";

function encode($text,$skey) {
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $skey, $text, MCRYPT_MODE_ECB, $iv);
    return base64_encode($crypttext);
}

echo encode($text,$skey);

Output:

dU1eOy+YwkYvm/KCTB8aqR1UsisyrHrvBu+E/H3G/s0aagMDKlNFekGXNQyFMFJD

NodeJS:

var crypto = require('crypto');

var text = "Some super mega text I want encode";
var skey = "rcbTw667C7zxghZ5U3gwhQlp22t8c5Rq";

function encode(text, skey) {
    var len = text.length;
    var padSize = 16 - ((len + 16 - 1) % 16 + 1);
    for (var i = 0; i < padSize; i++) { 
        text += '\0';
    }
    var cipher = crypto.createCipheriv('aes-256-ecb', skey, '');
    cipher.setAutoPadding(false);
    var encrypted = cipher.update(text, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    return encrypted;
}

console.log(encode(text, skey));

Output:

dU1eOy+YwkYvm/KCTB8aqR1UsisyrHrvBu+E/H3G/s0aagMDKlNFekGXNQyFMFJD

Also NodeJS mcrypt package works:

npm install mcrypt

Code:

var text = "Some super mega text I want encode";
var skey = "rcbTw667C7zxghZ5U3gwhQlp22t8c5Rq";

function encode(text, skey) {
    var MCrypt = require('mcrypt').MCrypt;
    var rijEcb = new MCrypt('rijndael-128', 'ecb');
    rijEcb.open(skey);
    var ciphertext = rijEcb.encrypt(text);
    return ciphertext.toString('base64');
}

console.log(encode(text, skey));

Output:

dU1eOy+YwkYvm/KCTB8aqR1UsisyrHrvBu+E/H3G/s0aagMDKlNFekGXNQyFMFJD
vlapo21
  • 334
  • 3
  • 3
1

I tried other option but nothing worked but This worked for me.

function encrypt(text) {
  text = '' + text;
  var crypto = require('crypto');
  var len = text.length;
  var padSize = 16 - (((len + 16 - 1) % 16) + 1);
  var data = String.fromCharCode(padSize);
  var text = text + data.repeat(padSize);

  var cipher = crypto.createCipheriv('aes-128-ecb','secretKey', '');
  cipher.setAutoPadding(false);
  var encrypted = cipher.update(text, 'utf8', 'base64');
  encrypted += cipher.final('base64');
  return encrypted;
}

Similer in PHP

function aes128Encrypt($str,$key){
$block = mcrypt_get_block_size('rijndael_128', 'ecb');
$pad = $block - (strlen($str) % $block);
$str .= str_repeat(chr($pad), $pad);
return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB));
}
Sandeep
  • 585
  • 7
  • 19
  • you saved my day . i was about makle an api in php and call it from nodejs .this saved my time and effort thanks dude – zabusa Jun 24 '21 at 08:27