AES-256-CTR Encryption in node JS and decryption in Java AES-256-CTR Encryption in node JS and decryption in Java node.js node.js

AES-256-CTR Encryption in node JS and decryption in Java


In your JS code, you're using the 32-character string d6F3231q7d19428743234@123nab@234 directly as the AES key, with each ASCII character directly mapped to a single key byte.

In the Java code, you're instead first hashing the same string with MD5, and then using the MD5 output as the AES key. It's no wonder that they won't match.

What you probably should be doing, in both cases, is either:

  1. randomly generating a string of 32 bytes (most of which won't be printable ASCII characters) and using it as the key; or
  2. using a key derivation function (KDF) to take an arbitrary input string and turn it into a pseudorandom AES key.

In the latter case, if the input string is likely to have less than 256 bits of entropy (e.g. if it's a user-chosen password, most of which only have a few dozen bits of entropy at best), then you should make sure to use a KDF that implements key stretching to slow down brute force guessing attacks.


Ps. To address the comments below, MD5 outputs a 16-byte digest, which will yield an AES-128 key when used as an AES SecretKeySpec. To use AES-256 in Java, you will need to provide a 32-byte key. If trying to use a 32-byte AES key in Java throws an InvalidKeyException, you are probably using an old version of Java with a limited crypto policy that does not allow encryption keys longer than 128 bits. As described this answer to the linked question, you will either need to upgrade to Java 8 update 161 or later, or obtain and install an unlimited crypto policy file for your Java version.


In the Java code you are taking the MD5 hash of secret before using it as a key:

MessageDigest md = MessageDigest.getInstance("MD5");byte[] thedigest = md.digest(secretBytes);SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");

Whereas, in your NodeJS code, you don't do this anywhere. So you're using two different keys when encrypting and decrypting.

Don't copy and paste code without understanding it. Especially crypto code.


Faced with the same task (but with 128, it easy to adapt for 256), here is working Java/NodeJs code with comments.

It's additionally wrapped to Base64 to readability, but it's easy to remove if you would like.Java side (encrypt/decrypt) :

import java.lang.Math; // headers MUST be above the first classimport java.util.Base64;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;import javax.crypto.Cipher;import javax.crypto.spec.SecretKeySpec;import javax.crypto.spec.IvParameterSpec;import java.nio.ByteBuffer;import java.util.Arrays;import java.nio.charset.StandardCharsets;// one class needs to have a main() methodpublic class MyClass{    private static void log(String s)    {        System.out.print("\r\n"+s);    }    public static SecureRandom IVGenerator() {       return new SecureRandom();    }  // arguments are passed using the text field below this editor  public static void  main(String[] args)  {    String valueToEncrypt = "hello, stackoverflow!";    String key = "3e$C!F)H@McQfTjK";    String encrypted = "";    String decrypted = "";    //ENCODE part    SecureRandom IVGenerator = IVGenerator();    byte[] encryptionKeyRaw = key.getBytes();    //aes-128=16bit IV block size    int ivLength=16;    byte[] iv = new byte[ivLength];    //generate random vector    IVGenerator.nextBytes(iv);    try {        Cipher encryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");        encryptionCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKeyRaw, "AES"), new IvParameterSpec(iv));        //encrypt        byte[] cipherText = encryptionCipher.doFinal(valueToEncrypt.getBytes());        ByteBuffer byteBuffer = ByteBuffer.allocate(ivLength + cipherText.length);        //storing IV in first part of whole message        byteBuffer.put(iv);        //store encrypted bytes        byteBuffer.put(cipherText);        //concat it to result message        byte[] cipherMessage = byteBuffer.array();        //and encrypt to base64 to get readable value        encrypted = new String(Base64.getEncoder().encode(cipherMessage));    } catch (Exception e) {        throw new IllegalStateException(e);    }    //END OF ENCODE CODE    log("encrypted and saved as Base64 : "+encrypted);    ///DECRYPT CODE :     try {        //decoding from base64        byte[] cipherMessageArr = Base64.getDecoder().decode(encrypted);        //retrieving IV from message        iv = Arrays.copyOfRange(cipherMessageArr, 0, ivLength);        //retrieving encrypted value from end of message        byte[] cipherText = Arrays.copyOfRange(cipherMessageArr, ivLength, cipherMessageArr.length);        Cipher decryptionCipher = Cipher.getInstance("AES/CTR/NoPadding");        IvParameterSpec ivSpec = new IvParameterSpec(iv);        SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKeyRaw, "AES");        decryptionCipher.init(Cipher.DECRYPT_MODE,secretKeySpec , ivSpec);        //decrypt        byte[] finalCipherText = decryptionCipher.doFinal(cipherText);        //converting to string        String finalDecryptedValue = new String(finalCipherText);        decrypted = finalDecryptedValue;    } catch (Exception e) {        throw new IllegalStateException(e);    }    log("decrypted from Base64->aes128 : "+decrypted);    //END OF DECRYPT CODE  }}

It could be easy be tested by online java compilers (this example prepared on https://www.jdoodle.com/online-java-compiler).

NodeJs decrypt side :

const crypto = require('crypto');const ivLength = 16;const algorithm = 'aes-128-ctr';const encrypt = (value, key) => {    //not implemented, but it could be done easy if you will see to decrypt    return value;};function decrypt(value, key) {    //from base64 to byteArray    let decodedAsBase64Value = Buffer.from(value, 'base64');            let decodedAsBase64Key = Buffer.from(key);    //get IV from message    let ivArr = decodedAsBase64Value.slice(0, ivLength);    //get crypted message from second part of message    let cipherTextArr = decodedAsBase64Value.slice(ivLength, decodedAsBase64Value.length);    let cipher = crypto.createDecipheriv(algorithm, decodedAsBase64Key, ivArr);    //decrypted value    let decrypted = cipher.update(cipherTextArr, 'binary', 'utf8');    decrypted += cipher.final('utf8');    return decrypted;}