Search code examples
javaaescryptojs

CryptoJs AES encryption and Java Decrypt


CryptoJS AES encryption and Java AES decryption I came across the above solution. I tried changing hashing ago from md5 to SHA-256, but it doesnt work. gives my the following error:

javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.

Is there anything i need to change in the helper function


Solution

  • 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);
            }
        }
    }