I used the answer in CryptoJS AES encryption and Java AES decryption as basis for a program that includes encryption and changes the MessageDigest from MD5 to SHA-256.
Running this program it sucessfully decrypts the encrypted data (as Base64-string in cipherText):
Changes: added encryption and using SHA-256 as hash algorithm
cipherText: AAAAAAAAAAAUUMNwaxNxbgvkWpqE+kfq0f3K/cQ6wwwiwFFsIBa8PXsoi0Z7dRhtNVurV1MbysOzNh9R9YMltSM/6en8e9JmEingdD3Rgp8=
The quick brown fox jumps over the lazy dog.
Running this program "stand alone" will work as expected but not as asked "encrypt in CryptoJS and decrypt in Java". That's because the MD5-hashing is "built in" (or "hard-coded") in CryptoJS as already commented by @Topaco some hours ago (all credits to him). So in the end there is no other way as to use MD5 on Java-side as CryptoJS uses it as MessageDigest. Maybe there is another crypto-library available for JavaScript.
Here is my code:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
import java.util.Base64;
public class MainOrgSha256WithEncryption {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
System.out.println("https://stackoverflow.com/questions/41432896/cryptojs-aes-encryption-and-java-aes-decryption");
System.out.println("Changes: added encryption and using SHA-256 as hash algorithm");
String secret = "René Über";
String plainText = "The quick brown fox jumps over the lazy dog. \uD83D\uDC7B \uD83D\uDC7B";
// generate random salt
SecureRandom secureRandom = new SecureRandom();
byte[] saltDataEncryption = new byte[8];
secureRandom.nextBytes(saltDataEncryption);
// changed MessageDigest from MD5 to SHA-256
MessageDigest md5 = MessageDigest.getInstance("SHA-256");
//MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIVEncryption = GenerateKeyAndIV(32, 16, 1, saltDataEncryption, secret.getBytes(StandardCharsets.UTF_8), md5);
SecretKeySpec keyEncryption = new SecretKeySpec(keyAndIVEncryption[0], "AES");
IvParameterSpec ivEncryption = new IvParameterSpec(keyAndIVEncryption[1]);
// encryption
Cipher aesCBCEncryption = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBCEncryption.init(Cipher.ENCRYPT_MODE, keyEncryption, ivEncryption);
byte[] cipherTextEncryption = aesCBCEncryption.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
// concate 8 zero bytes + salt + cipherTextEncryption
int arrayLength = 8 + saltDataEncryption.length + cipherTextEncryption.length;
byte[] cipherTextEncryptionComplete = new byte[arrayLength];
System.arraycopy(saltDataEncryption, 0, cipherTextEncryptionComplete, 8, saltDataEncryption.length);
System.arraycopy(cipherTextEncryption, 0, cipherTextEncryptionComplete, 16, cipherTextEncryption.length);
String cipherTextBase64 = Base64.getEncoder().encodeToString(cipherTextEncryptionComplete);
// now we are using the new cipherTextBase64 as input for the decryption
String cipherText = cipherTextBase64;
System.out.println("cipherText: " + cipherText);
// decryption
byte[] cipherData = Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
System.out.println(decryptedText);
}
/**
* Generates a key and an initialization vector (IV) with the given salt and password.
* <p>
* This method is equivalent to OpenSSL's EVP_BytesToKey function
* (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
* By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
* </p>
* @param keyLength the length of the generated key (in bytes)
* @param ivLength the length of the generated IV (in bytes)
* @param iterations the number of digestion rounds
* @param salt the salt data (8 bytes of data or <code>null</code>)
* @param password the password data (optional)
* @param md the message digest algorithm to use
* @return an two-element array with the generated key and IV
*/
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte)0);
}
}
}