-2

I have the following simple encryption code running in node.js:

var crypto = require('crypto');

var encKey = "FOO"; // Not the real key. Assume it works though.

var encrypt = function(str) {
  var cipher = crypto.createCipher('aes-256-cbc', encKey);
  var crypted = cipher.update(str, 'utf-8', 'hex');
  crypted += cipher.final('hex');
  return crypted;
};

Which I can also decrypt as below:

var crypto = require('crypto');

var encKey = "FOO"; // Not the real key. Assume it works though.

var decrypt = function(str) {
  var decipher = crypto.createDecipher('aes-256-cbc', encKey);
  var decrypted = decipher.update(str, 'hex', 'utf-8');
  decrypted += decipher.final('utf-8');
  return decrypted;
};

This all works fine. Strings are encrypting and decrypting as expected. But now I am faced with task of decrypting encrypted strings from this node.js code, in Java. And that is where things are going wrong and I am not sure why.

For decryption, My Java code looks like this:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import java.security.MessageDigest;
import java.util.Arrays;

private static final String encKey = "FOO";
private static SecretKeySpec secretKey;
private static byte[] key;

public static String decrypt(String str) throws Exception {
  String hexDecodedStr = new String(Hex.decodeHex(str.toCharArray()));
  setKey(encKey);
  Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
  cipher.init(Cipher.DECRYPT_MODE, secretKey);
  return new String(cipher.doFinal(hexDecodedStr.getBytes()));
}

private static void setKey(String myKey) throws Exception {
  MessageDigest sha = null;
  try {
    key = myKey.getBytes("UTF-8");
    sha = MessageDigest.getInstance("SHA-1");
    key = sha.digest(key);
    key = Arrays.copyOf(key, 16); 
    secretKey = new SecretKeySpec(key, "AES");
  } 
  catch (Exception e) {
    throw e;
  } 
}

And it doesn't work. It seems like no matter what I try, I end up with some exception on the cipher.doFinal() call, or the String I get back is totally wrong. I know the node.js code is using aes-256-cbc, while the Java code is using AES/ECB/PKCS5Padding instead of AES/CBC/PKCS5Padding, but when I tried to use AES/CBC/PKCS5Padding, it was requiring an InitVector which I didn't have in node.js so I was unsure of how to proceed. Is node making an InitVector under the hood if not provided with one? Am I missing something totally obvious?

RP31
  • 1
  • 1
  • 1
    _"I end up with some exception on the cipher.doFinal() call"_ -- well, share the COMPLETE stack trace please. – Jim Garrison Jan 01 '18 at 04:40
  • 1
    Your NodeJS code is so broken. You should probably fix that first. – Luke Joshua Park Jan 01 '18 at 05:01
  • @lukepark there are more things broken :/ – gusto2 Jan 01 '18 at 05:33
  • @lukepark I am curious to hear more about this broken code, since it is working properly for me. Perhaps I have omitted a line or two from the original source that would help? – RP31 Jan 01 '18 at 06:08
  • I mean broken as in invoking undefined behavior. CBC mode should only be used with `createCipheriv`. What you are doing now is not actually defined anywhere, it could produce different outputs across versions and/or platforms. Not to mention encryption without an IV is insecure and vulnerable to a number of trivial attacks. You should make your NodeJS code secure and correct before translating elsewhere. – Luke Joshua Park Jan 01 '18 at 06:12
  • @LukePark Your point about the undefined behavior is valid. Thanks. It also hints at my original question, what is node doing under the hood in this case w/o a provided IV? If I knew that, I could probably get CBC mode to decrypt these strings in Java, and include an IV in node. Regarding encryption w/o an IV, I am aware of its shortcomings but that is practically irrelevant in this case. The resulting string is easily discoverable by an end user and the source string does not contain sensitive information. This was done simply to obfuscate and later corrupt data if a user tampered with it. – RP31 Jan 01 '18 at 06:35
  • Yes [createCipher derives key AND IV from password](https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options) -- with MD5 not SHA1. Also, pure encryption is not designed to prevent tampering, and although tampering CBC is less trivial than some other modes, a competent adversary can do it much more often than you think. If integrity is important, use something designed to protect it like a MAC, signature or authenticated encryption like AEAD, don't rely on plain encryption. – dave_thompson_085 Jan 01 '18 at 07:25
  • Have a look at the [code examples given here](https://github.com/luke-park/SecureCompatibleEncryptionExamples) (disclaimer: I wrote them). There are examples for NodeJS and Java that are fully compatible and are actually secure. – Luke Joshua Park Jan 01 '18 at 12:45

1 Answers1

0

You seems to have the same issue as others OpenSSL encryption failing to decrypt C#

As far I understood the docs, the crypto libeary uses openssl. The openssl creates IV and key from the password using its EVP_BytesToKey function and random salt (not just hash). As dave pointed out, the crypto library uses no salt.

the output of openssl is Salted_{8 bytes salt}{ciphertext} so check what is output of the cipher ( I am unable to do it now)

I wrote a small article how to encrypt properly in Java

gusto2
  • 11,210
  • 2
  • 17
  • 36
  • nodejs crypto uses OpenSSL library, per the [doc for createCipher](https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options) including EVP_BytesToKey _without salt_, but not OpenSSL _commandline_ and thus not the file format with `Salted__`. – dave_thompson_085 Jan 01 '18 at 07:17
  • @dave_thompson_085 thank you for pointing out. IMHO that's pretty dangerous (mainly for people who don't know what are they doing. Maybe `createCipheriv` would be safer to use – gusto2 Jan 01 '18 at 07:59