Search code examples
javascriptnode.jsencryptioncryptojsnode-crypto

Compatibility between node crypto and crypto-js encryption and decryption


How do I properly encrypt/decrypt data between node.js with the crypto module (server side) and crypto-js (client side, react-native)?

NOTE: I'm using cryptojs in a react-native project, thus I can't use crypto on client. replacing crypto server side is not an option for me.

Server-side code:

var Crypto = require("crypto");

var Cipher = {
  pass: "0123456789abcdef0123456789abcdef",
  iv: "0123456789abcdef",
  encript: function (msg) {
    try {
      var cipher = Crypto.createCipheriv("aes-256-cbc", this.pass, this.iv);
      var hash = cipher.update(msg, 'utf8', "hex");
      var hex = hash + cipher.final("hex");
      return hex;
    } catch (err) {
      console.error(err);
      return "";
    }
  },
  decript: function (hex){
    try {
      var decipher = Crypto.createDecipheriv("aes-256-cbc", this.pass, this.iv);
      var dec = decipher.update(hex, "hex", 'utf8');
      return dec + decipher.final('utf8');
    } catch (err) {
      console.error(err);
      return "";
    }
  }
}
Cipher.encript("i have an apple"); // 577267026f88f82ea286baf6bf089acb
Cipher.decript("577267026f88f82ea286baf6bf089acb"); // i have an apple

Client-side code

var CryptoJS = require("crypto-js");
var Cipher = {
  pass: CryptoJS.enc.Hex.parse("0123456789abcdef0123456789abcdef"),
  iv: CryptoJS.enc.Hex.parse("0123456789abcdef"),
  encript: function (msg) {
    try {
      var options = { mode: CryptoJS.mode.CBC, iv: this.iv};
      var json = CryptoJS.AES.encrypt(msg, this.pass, options);
      return json.ciphertext.toString(CryptoJS.enc.Hex);
    } catch (err) {
      console.error(err);
      return "";
    }
  },
  decript: function (hex){
    try {
      // ???????????????????????????????????
      // ???????????????????????????????????

    } catch (err) {
      console.error(err);
      return "";
    }
  }
}

Cipher.encript("i have an apple"); // 405552d9a77ea9e29442057d27cd7aee
Cipher.decript(?????);  // I have no Idea

Solution

  • Your "password" (it is used as a key instead of a password) has two different encodings on client-side and server-side. On the client, you're parsing it as Hex, but on the server, you're passing it as a binary string, which is used as-is.

    You either need parse it on the server (now it is AES-128 and not AES-256):

    pass: new Buffer("0123456789abcdef0123456789abcdef", "hex"),
    

    Or change the encoding on the client (from AES-128 to AES-256):

    pass: CryptoJS.enc.Utf8.parse("0123456789abcdef0123456789abcdef"),

      encript: function (msg) {
        try {
          var options = { mode: CryptoJS.mode.CBC, iv: this.iv};
          var json = CryptoJS.AES.encrypt(msg, this.pass, options);
          return json.ciphertext.toString(CryptoJS.enc.Hex);
        } catch (err) {
          console.error(err);
          return "";
        }
      },
      decript: function (hex){
        try {
          var options = { mode: CryptoJS.mode.CBC, iv: this.iv};
          var json = CryptoJS.AES.decrypt({
            ciphertext: CryptoJS.enc.Hex.parse(hex)
          }, this.pass, options);
          return json.toString(CryptoJS.enc.Utf8);
        } catch (err) {
          console.error(err);
          return "";
        }
      }
    

    You still have security problems:

    • The IV has to be randomly chosen for every encryption under the same key to achieve semantic security. It doesn't have to be secret, so you can simply send it along with the ciphertext. It's common to prepend it to the ciphertext and slice it off before decryption.

    • It is better to authenticate your ciphertexts so that attacks like a padding oracle attack are not possible. This can be done with authenticated modes like GCM or EAX, or with an encrypt-then-MAC scheme with a strong MAC like HMAC-SHA256, which CryptoJS provides.