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:
- randomly generating a string of 32 bytes (most of which won't be printable ASCII characters) and using it as the key; or
- 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;}