14

I have an encrypt-code in Java. I'm trying to port the encrypt part to node. Basically, node will do the encryption using the crypto module, and then Java will do the decryption.

Here's how I do encryption in Java:

protected static String encrypt(String plaintext) {
    final byte[] KEY = {
            0x6d, 0x79, 0x56, 0x65, 0x72, 0x79, 0x54, 0x6f, 0x70,
            0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x4b
    };

    try {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        final SecretKeySpec secretKey = new SecretKeySpec(KEY, "AES");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        final String encryptedString = Base64.encodeToString(
            cipher.doFinal(plaintext.getBytes()), Base64.DEFAULT);

        return encryptedString;
    } catch (Exception e) {
        return null;
    }
}

Here's how I do encryption in node:

var crypto = require('crypto'),
    key = new Buffer('6d7956657279546f705365637265744b', 'hex'),
    cipher = crypto.createCipher('aes-128-ecb', key),
    chunks = [];

cipher.setAutoPadding(true);
chunks.push(cipher.update(
    new Buffer(JSON.stringify({someKey: "someValue"}), 'utf8'),
    null, 'base64'));
chunks.push(cipher.final('base64'));

var encryptedString = chunks.join('');

In Java, I get the string T4RlJo5ENV8h1uvmOHzz1KjyXzBoBuqVLSTHsPppljA=. This gets decrypted correctly. However, in node, I get al084hEpTK7gOYGQRSGxF+WWKvNYhT4SC7MukrzHieM= which is obviously different and thus it won't get decrypted correctly.

I tried to look for people who has the same problem as me, and this github issue is the closest I can find. As suggested in that issue, I tried running openssl like so:

$ echo -e '{"someKey": "someValue"}' | openssl enc -a -e -aes-128-ecb -K "6d7956657279546f705365637265744b"
T4RlJo5ENV8h1uvmOHzz1MY2bhoFRHZ+ClxsV24l2BU=

The result I got was close enough to the one produced by java, but still different:

T4RlJo5ENV8h1uvmOHzz1MY2bhoFRHZ+ClxsV24l2BU=  // openssl
T4RlJo5ENV8h1uvmOHzz1KjyXzBoBuqVLSTHsPppljA=  // java
al084hEpTK7gOYGQRSGxF+WWKvNYhT4SC7MukrzHieM=  // node

Which brings me to the question, how do I make node output the same encrypted string as my java code? I can only change my code in node, but not in java.

Jason C
  • 38,729
  • 14
  • 126
  • 182
avendael
  • 2,459
  • 5
  • 26
  • 30
  • What padding format is each one using? How do you know that the content being encrypted is actually identical on each platform (I see no reason to assume that Node's `JSON.stringify` output is identical to whatever you're passing to Java as `plaintext`)? – chrylis -cautiouslyoptimistic- Oct 31 '13 at 04:59
  • Java is using PKCS5Padding. From what I've read, openssl is also using the same (sorry I lost the link). For node, I'm not sure how to specify the padding, which is why I opted to use `cipher.setAutoPadding(true)`. EDIT: Found the link. See the accepted answer [here](http://stackoverflow.com/questions/10548973/encrypting-and-decrypting-with-python-and-nodejs) – avendael Oct 31 '13 at 05:03
  • Both inputs are identical. When I tested this again, I passed `JSON.stringify`'s output (manually) to java `encrypt` with some escape characters: `encrypt("{\"someKey\":\"someValue\"}")` – avendael Oct 31 '13 at 05:11
  • 1
    You need to compare the byte arrays. The same-looking text can be encoded in different bytes in different character sets, and `String#getBytes` explicitly depends on the platform character set. Dump the byte arrays. – chrylis -cautiouslyoptimistic- Oct 31 '13 at 05:19
  • Ok, that took a while. Here's the dumped byte array in node produced by `Buffer(JSON.stringify({someKey:"someValue"})).toString('hex')`: `7b22736f6d654b6579223a22736f6d6556616c7565227d`. Here's the dumped byte array in java, produced by `Arrays.toString("{\"someKey\":\"someValue\"}".getBytes()))`: `[123, 34, 115, 111, 109, 101, 75, 101, 121, 34, 58, 34, 115, 111, 109, 101, 86, 97, 108, 117, 101, 34, 125]`. I have translated the java dump to hex: `[7b, 22, 73, 6f, 6d, 65, 4b, 65, 79, 22, 3a, 22, 73, 6f, 6d, 65, 56, 61, 6c, 75, 65, 22, 7d]`. – avendael Oct 31 '13 at 05:44
  • 1
    Without the containing array, the java dump looks like this: `7b22736f6d654b6579223a22736f6d6556616c7565227d` which is the same as how it looks like in node. Probably, the problem lies somewhere else. – avendael Oct 31 '13 at 05:45
  • FYI the reason your openssl commandline result was different is that `echo` adds a newline to the data. Depending on your platform, `echo -n` (instead of `echo -e`) might have solved this. – dave_thompson_085 May 12 '18 at 09:52

4 Answers4

13

Thought I post the a full CBC example from both the node and java sides(256 instead of 128): If you get the java.security.InvalidKeyException you have to install the Java Cryptography Extension (JCE) unlimited strength jurisdiction policy files:

Java 6 link Java 7 link Java 8 link

Java encrypt and Decrypt.

    import java.security.MessageDigest;
    import javax.crypto.spec.SecretKeySpec;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.Cipher;
    import java.util.Base64;
    import javax.xml.bind.DatatypeConverter;

    public class AESExample {
        private static byte[] iv = "0000000000000000".getBytes();
        private static String decrypt(String encrypted, String seed)
                throws Exception {
            byte[] keyb = seed.getBytes("utf-8");
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] thedigest = md.digest(keyb);
            SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
            Cipher dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            dcipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(seed.getBytes("UTF-8"), "AES"), new IvParameterSpec(iv));
            byte[] clearbyte = dcipher.doFinal(DatatypeConverter
                    .parseHexBinary(encrypted));
            return new String(clearbyte);
        }
        public static String encrypt(String content, String key) throws Exception {
            byte[] input = content.getBytes("utf-8");
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] thedigest = md.digest(key.getBytes("utf-8"));
            SecretKeySpec skc = new SecretKeySpec(thedigest, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes("UTF-8"), "AES"), new IvParameterSpec(iv));
            byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
            int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
            ctLength += cipher.doFinal(cipherText, ctLength);
            return DatatypeConverter.printHexBinary(cipherText);
        }

public static String encrypt128(String content, String key) throws Exception {
        byte[] input = content.getBytes("utf-8");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(DatatypeConverter.parseHexBinary(key), "AES"), new IvParameterSpec(iv));
         byte[] encrypted = cipher.doFinal(content.getBytes("UTF-8"));
        return DatatypeConverter.printHexBinary(encrypted);
    }

        public static void main(String[] args) throws Exception {
            String data = "Here is my string";
            String key = "1234567891123456";
            String cipher = AESExample.encrypt(data, key);
            String decipher = AESExample.decrypt(cipher, key);
            System.out.println(cipher);
            System.out.println(decipher);
            System.out.println(AESExample.encrypt(data, "1234567891123456"));
            System.out.println(AESExample.encrypt128(data, "d7900701209d3fbac4e214dfeb5f230f"));
        }
    }

Node both directions below:

    var crypto = require('crypto');
        var iv = new Buffer('0000000000000000');
        // reference to converting between buffers http://nodejs.org/api/buffer.html#buffer_new_buffer_str_encoding
        // reference node crypto api http://nodejs.org/api/crypto.html#crypto_crypto_createcipheriv_algorithm_key_iv
        // reference to ECB vs CBC cipher methods http://crypto.stackexchange.com/questions/225/should-i-use-ecb-or-cbc-encryption-mode-for-my-block-cipher

        var encrypt = function(data, key) {
          var decodeKey = crypto.createHash('sha256').update(key, 'utf-8').digest();
          var cipher = crypto.createCipheriv('aes-256-cbc', decodeKey, iv);
          return cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
        };

        var decrypt = function(data, key) {
          var encodeKey = crypto.createHash('sha256').update(key, 'utf-8').digest();
          var cipher = crypto.createDecipheriv('aes-256-cbc', encodeKey, iv);
          return cipher.update(data, 'hex', 'utf8') + cipher.final('utf8');
        };

       var decrypt128 = function(data, key) {

          var encodeKey = crypto.createHash('sha256').update(key, 'utf-8').digest();
         var cipher = crypto.createDecipheriv('aes-128-cbc', new Buffer(key, 'hex'),
    new Buffer(
      iv));
  return cipher.update(data, 'hex', 'utf8') + cipher.final('utf8');
};
        var data = 'Here is my string'
        var key = '1234567891123456';
        var cipher = encrypt(data, key);
        var decipher = decrypt(cipher, key);
        console.log(cipher);
        console.log(decipher);
        // the string below was generated from the "main" in the java side
        console.log(decrypt(
          "79D78BEFC06827B118A2ABC6BD9D544E83F92930144432F22A6909EF18E0FDD1", key));
        console.log(decrypt128(
  "3EB7CF373E108ACA93E85D170C000938A6B3DCCED53A4BFC0F5A18B7DDC02499",
  "d7900701209d3fbac4e214dfeb5f230f"));
Matthew Payne
  • 2,996
  • 2
  • 19
  • 15
  • @TerryLennox it worked, but I made two changes in your code only after that it worked so please edit it, first one is you are not using `skc` present in java code `encrypt` function, second in node code after changing `hex` to `base64` it woked, Thanks a lot man bdw cheers. – Sudhanshu Gaur Oct 02 '18 at 17:58
  • skc is not used in java code .Is it in place of "new SecretKeySpec(key.getBytes("UTF-8") " in cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes("UTF-8"), "AES"), new IvParameterSpec(iv)); should be used ? – manjunath kallannavar Nov 09 '18 at 11:57
  • When i use your js code to encrypt your demo string, i get fccb0c3810043aa268099a07d694fad3e0561c7b93fbbcfb3868d2200239ae51 and when i tried to decrypt with your java code (key remaining the same as given in your sample, i get the error: Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption. – Sathish Kumar May 01 '19 at 13:50
  • **DatatypeConfigurationException:** Provider org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl not found – Iman Marashi Jul 06 '19 at 06:33
10

Finally, I found the solution to my problem. Thanks to this guy. The key to the solution is the initialization vector. Quoting the gist:

// ECB mode won't need IV, so keep it like this and it will work well.

Here's how the solution looks like:

var crypto = require('crypto'),
    iv = new Buffer(''),
    key = new Buffer('6d7956657279546f705365637265744b', 'hex'),
    cipher = cypto.createCipheriv('aes-128-ecb', key, iv),
    chunks = [];

chunks.push(cipher.update(
    new Buffer(JSON.stringify({someKey: "someValue"}), 'utf8'),
    'buffer', 'base64'));
chunks.push(cipher.final('base64'));
var encryptedString = chunks.join('');
avendael
  • 2,459
  • 5
  • 26
  • 30
  • 2
    That was not really the issue, the issue is that `createCipher` uses a password from which the key is *derived* (the IV is derived as well, but that is ignored for ECB). The `createCipheriv` method however directly uses a key as bytes. So the solution is correct (+1) but the reason why it is correct isn't :P – Maarten Bodewes Oct 31 '13 at 15:55
  • What could be the alternate solution then? – avendael Nov 01 '13 at 12:01
  • 1
    You've got the correct solution, as indicated in the comment. AFAIK there is no alternative. – Maarten Bodewes Nov 01 '13 at 16:18
  • 1
    The difference between `createCipher` and `createCipheriv` caught me out too (as well as the fact that in Java `"AES"` implies ECB whereas in Node/OpenSSL, `"aes128"` implies CBC). Thanks to @MaartenBodewes for the tip. – chris Apr 26 '16 at 09:34
  • To me, using ``createCipheriv`` with ``new Buffer('')`` instead of ``createCipher`` made all the difference... ! – uthomas May 24 '16 at 15:37
  • what is this someKey: "someValue" please help me i am stuck in this encryption thing from last 2 weeks ?? – Sudhanshu Gaur Jun 30 '16 at 22:41
5

Working example of encrypting in Node.Js and decrypting in Java:

to encrypt:

var crypto = require('crypto')
var cipher = crypto.createCipher('aes-128-ecb','somepassword')
var text = "the big brown fox jumped over the fence"
var crypted = cipher.update(text,'utf-8','hex')
crypted += cipher.final('hex')
//now crypted contains the hex representation of the ciphertext

to decrypt:

private static String decrypt(String seed, String encrypted) throws Exception {
    byte[] keyb = seed.getBytes("UTF-8");
    MessageDigest md = MessageDigest.getInstance("MD5");
    byte[] thedigest = md.digest(keyb);
    SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");
    Cipher dcipher = Cipher.getInstance("AES");
    dcipher.init(Cipher.DECRYPT_MODE, skey);

    byte[] clearbyte = dcipher.doFinal(toByte(encrypted));
    return new String(clearbyte);
}

private static byte[] toByte(String hexString) {
    int len = hexString.length()/2;
    byte[] result = new byte[len];
    for (int i = 0; i < len; i++) {
        result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
    }
    return result;
}
Dima Gutzeit
  • 221
  • 4
  • 3
0

Important : Matthew Payne answer just work for "Encrypt in node and decrypt in java" not in both so don't just copy and paste if you want both

wizmea
  • 1,079
  • 1
  • 8
  • 8