0

I am trying to encrypt data using crypto-js javascript library and trying to decrypt the same encrypted text on nodejs side using node crypto library. I am using AES 256 encryption algo with CTR mode with no padding. I am able to encrypt properly but the description on nodejs crypto module is not producing same plain text.

If I try to encrypt or decrypt using the same crypto-js and node crypto library, it works fine but encryption on crypto-js and description on crypto is not working as expected. I have tried to confirm if I encrypt and decrypt in the same library than it works or not and it works perfectly fine. Can someone please check what mistake I am making here?

Please find below code samples.

Encryption:

var key =  CryptoJS.enc.Hex.parse('F29BA22B55F9B229CC9C250E11FD4384');
var iv  =  CryptoJS.enc.Hex.parse('C160C947CD9FC273');


function encrypt(plainText) {

  return CryptoJS.AES.encrypt(                                               
                                plainText,                                           
                                key,
                                { 
                                    iv: iv,
                                    padding: CryptoJS.pad.NoPadding,
                                    mode:  CryptoJS.mode.CTR
                                }
                              );                             
}

Descryption using NodeJS crypo module:

var algorithm = 'aes-256-ctr';
var key = 'F29BA22B55F9B229CC9C250E11FD4384';
var iv = 'C160C947CD9FC273';

var outputEncoding = 'hex';
var inputEncoding = 'hex';

const decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypted = decipher.update('8df5e11f521cf492437a95', inputEncoding, 'utf8');
decrypted += decipher.final('utf8');

console.log(decrypted);

As I have mentioned above, I have JavaScript crypo-js and NodeJS crypo module sessions working fine if I encrypt and decrypt using the same lib but doesn't work otherwise. Please check the working code as below.

JavaScript: http://jsfiddle.net/usr_r/2qwt8jsh/2/

NodeJS: https://repl.it/repls/AchingRegalPhp

user3314492
  • 233
  • 5
  • 17

2 Answers2

1

I think your CryptoJS code isn't using AES-256, as the key and IV are too short and hence it's implicitly using AES-128. if you get the blockSize from the CryptoJS.AES object it says 4 for me. that said I don't know CryptoJS very well and that might not mean "4 words".

To bypass this implementation uncertainty, it's good to have a "gold standard" to replicate. NIST provides lots of test vectors, some of which apply to your CTR mode AES-256. First I pull out a set of (hex encoded) test vectors from that document:

const key = (
  '603deb1015ca71be2b73aef0857d7781' +
  '1f352c073b6108d72d9810a30914dff4'
)
const ctr = 'f0f1f2f3f4f5f6f7f8f9fafbfcfdff00'

const output = '5a6e699d536119065433863c8f657b94'
const cipher = 'f443e3ca4d62b59aca84e990cacaf5c5'
const plain = 'ae2d8a571e03ac9c9eb76fac45af8e51'

next I try and recover these from Node's crypto module:

const crypto = require('crypto')

function node_crypto(text) {
  const dec = crypto.createDecipheriv(
    'aes-256-ctr',
    Buffer.from(key, 'hex'),
    Buffer.from(ctr, 'hex')
  );
  const out = dec.update(Buffer.from(text, 'hex'))
  return out.toString('hex')
}

now I can write a simple test harness for testing the above and use it with that function:

const zero = '00'.repeat(16);
function test_crypto(fn) {
  return {
    'zero => output': fn(zero) == output,
    'cipher => plain': fn(cipher) == plain,
    'plain => cipher': fn(plain) == cipher,
  }
}

console.log(test_crypto(node_crypto))

which gives me true for all tests.

finally, the equivalent code for CryptoJS is:

const CryptoJS = require("crypto-js");

function cryptojs(text) {
  const out = CryptoJS.AES.encrypt(
    CryptoJS.enc.Latin1.parse(Buffer.from(text, 'hex').toString('binary')),
    CryptoJS.enc.Hex.parse(key),
    {
      iv: CryptoJS.enc.Hex.parse(ctr),
      mode:  CryptoJS.mode.CTR,
      padding: CryptoJS.pad.NoPadding,
    }
  );

  return out.ciphertext.toString();
}

console.log(test_crypto(cryptojs))

which also works for me.

It's important to note that CryptoJS just silently accepts arbitrarily sized keys, with the docs saying:

CryptoJS supports AES-128, AES-192, and AES-256. It will pick the variant by the size of the key you pass in. If you use a passphrase, then it will generate a 256-bit key.

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
0

In contrast to the NodeJS-code (Crypto), the JavaScript-code (CryptoJS) interprets keys and IV as hexadecimal strings. Therefore, in the JavaScript-Code AES-128 is used and in the NodeJS-Code AES-256. To solve the problem, both codes must use the same encryption.

Option 1: Change the JavaScript-code to AES-256: Replace in the JavaScript-code

var key = CryptoJS.enc.Hex.parse('F18AB33A57F9B229CC9C250D00FC3273');
var iv = CryptoJS.enc.Hex.parse('D959B836CD9FB162');

by

var key = CryptoJS.enc.Utf8.parse('F18AB33A57F9B229CC9C250D00FC3273');
var iv = CryptoJS.enc.Utf8.parse('D959B836CD9FB162');

Option 2: Change the NodeJS-code to AES-128: Replace in the NodeJS-code

var algorithm = 'aes-256-ctr';    
var key = 'F18AB33A57F9B229CC9C250D00FC3273';
var iv = 'D959B836CD9FB162';

by

var algorithm = 'aes-128-ctr';
var key = Buffer.from('F18AB33A57F9B229CC9C250D00FC3273', 'hex');
var iv = Buffer.from('D959B836CD9FB1620000000000000000', 'hex');

With one of each of the two changes, the codes of both links produce the same result.


If AES-256 should be used and key and IV should be specified as hexadecimal strings, a correspondingly large key and IV must be used, e.g. on the JavaScript-side:

var key = CryptoJS.enc.Hex.parse('F18AB33A57F9B229CC9C250D00FC3273F18AB33A57F9B229CC9C250D00FC3273');
var iv = CryptoJS.enc.Hex.parse('D959B836CD9FB16200000000000000'); 

and on the NodeJS-side:

var algorithm = 'aes-256-ctr';
var key = Buffer.from('F18AB33A57F9B229CC9C250D00FC3273F18AB33A57F9B229CC9C250D00FC3273', 'hex');
var iv = Buffer.from('D959B836CD9FB1620000000000000000', 'hex');
Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Thank you for feedback. Let me try this but what if I wanted to use aes-256-ctr rather than aes 128? We wanted to use 256 bit enc and not 128. – user3314492 Sep 13 '19 at 20:31
  • I tried doing that and getting "Error: Invalid IV length" error while decrypting it. – user3314492 Sep 13 '19 at 20:37
  • Which of the two changes did you try? – Topaco Sep 13 '19 at 20:38
  • Aha. I think that did the trick. Initially, I changed at both places and won't work but removed change at NodeJS and it works. Let me try to test this little bit more and update here. Thank you again @Topaco on this. – user3314492 Sep 13 '19 at 20:43
  • AES has always 128-bit block size. The appended AES-128 and AES-256 are the keys sizes. – kelalaka Sep 13 '19 at 20:44
  • @user3314492 - Maybe it's more understandable this way. Furthermore, in the previous version a 0-byte was missing in the IV. (correct is: D959B836CD9FB162000000000000000000, now fixed). – Topaco Sep 13 '19 at 21:39