22

I'm only asking this because I have read many posts for 2 days now about crypto AES encryption, and just when I thought I was getting it, I realized I wasn't getting it at all.

This post is the closest one to my issue, I have exactly the same problem but it is unanswered:

CryptoJS AES encryption and JAVA AES decryption value mismatch

I have tried doing it in many ways but I haven't gotten it right.

First Off

I'm getting the already encrypted string (I only got the code to see how they were doing it), so modifying the encryption way is not an option. That's why all the similar questions aren't that useful to me.

Second

I do have access to the secret key and I can modify it (so adjusting length is an option if neccessary).

The encryption is done on CryptoJS and they send the encrypted string as a GET parameter.

GetParamsForAppUrl.prototype.generateUrlParams = function() {
const self = this;
 return new Promise((resolve, reject) => {
   const currentDateInMilliseconds = new Date().getTime();
   const secret = tokenSecret.secret;
   var encrypted = CryptoJS.AES.encrypt(self.authorization, secret);
   encrypted = encrypted.toString();
   self.urlParams = {
     token: encrypted,
     time: currentDateInMilliseconds
   };
   resolve();
 });
};

I can easily decrypt this on javascript using CryptoJS with:

var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret);
    console.log(decrypted.toString(CryptoJS.enc.Utf8)); 

But I don't want to do this on Javascript, for security reasons, so I'm trying to decrypt this on Java:

String secret = "secret";
byte[] cipherText = encrypted_string.getBytes("UTF8");
SecretKey secKey = new SecretKeySpec(secret.getBytes(), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String myDecryptedText = = new String(bytePlainText);

Before I had any idea of what I was doing, I tried base64 decoding, adding some IV and a lot of stuff I read, of course none of it worked.

But after I started to understand, kinda, what I was doing, I wrote that simple script above, and got me the same error on the post: Invalid AES key length

I don't know where to go from here. After reading a lot about this, the solution seems to be hashing or padding, but I have no control on the encryption method, so I can't really hash the secret or pad it.

But as I said, I can change the secret key so it can match some specific length, and I have tried changing it, but as I'm shooting in the dark here, I don't really know if this is the solution.

So, my question basically is, If I got the encrypted string (in javascript like the first script) and the secret key, is there a way to decrypt it (in Java)? If so, how to do it?

Community
  • 1
  • 1
Lauro182
  • 1,597
  • 3
  • 15
  • 41

2 Answers2

66

Disclaimer: Do not use encryption unless you understand encryption concepts including chaining mode, key derivation functions, IV and block size. And don't roll your own security scheme but stick to an established one. Just throwing in encryption algorithms doesn't mean an application has become any more secure.

CryptoJS implements the same key derivation function as OpenSSL and the same format to put the IV into the encrypted data. So all Java code that deals with OpenSSL encoded data applies.

Given the following Javascript code:

var text = "The quick brown fox jumps over the lazy dog.  ";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);

We get the cipher text:

U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=

On the Java side, we have

String secret = "René Über";
String cipherText = "U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";

byte[] cipherData = Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);

MessageDigest md5 = MessageDigest.getInstance("MD5");
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);

The result is:

The quick brown fox jumps over the lazy dog.  

That's the text we started with. And emojis, accents and umlauts work as well.

GenerateKeyAndIV is a helper function that reimplements OpenSSL's key derivation function EVP_BytesToKey (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).

/**
 * 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);
    }
}

Note that you have to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy. Otherwise, AES with key size of 256 won't work and throw an exception:

java.security.InvalidKeyException: Illegal key size

Update

I have replaced Ola Bini's Java code of EVP_BytesToKey, which I used in the first version of my answer, with a more idiomatic and easier to understand Java code (see above).

Also see How to decrypt file in Java encrypted with openssl command using AES?.

Codo
  • 75,595
  • 17
  • 168
  • 206
  • I already have JCE installed, and I have read a bit about the key size, I believe there was a way to upgrade it to 256, but I don't mind about size, 128 is fine. This looks very promising, will give it a try tomorrow and get back to you, thank you. – Lauro182 Jan 03 '17 at 01:15
  • If you can't change how the data is encrypted, then you cannot choose the key length. I'll have to work with 256 bits. – Codo Jan 03 '17 at 07:29
  • Ok, so, I downloaded the file, backed up current files and replaced them. I restarted application, restarted computer, but I'm still getting: Illegal key size at aesCBC.init(Cipher.DECRYPT_MODE, key, iv); – Lauro182 Jan 04 '17 at 20:24
  • It seems your Unlimited Strength Jurisdiction Policy isn't properly installed. Ask a separate question on SO specifically about this. – Codo Jan 04 '17 at 20:32
  • Let me do a bit more explain, It is a netbeans project, I realized that on project properties, on Sources, the source/binary format is on JDK 7, don't know if this affect at all, but on my machine, I have 2 jvms installed, java 8, openjdk and oracle, oracle is running. I don't know how project source is JDK 7. – Lauro182 Jan 04 '17 at 20:33
  • Ask a new question. – Codo Jan 04 '17 at 20:36
  • After solving the unlimited strength jurisdiction policy problem, the code worked perfectly, thank you! – Lauro182 Jan 05 '17 at 18:21
  • 4
    How do you encrypt same data with java, Can you please post of encryption as well. – Amarjit Aug 29 '17 at 08:52
  • Is its java version available for encryption as well? Please do the needful – Harneet Kaur Aug 31 '17 at 08:22
  • I am trying to use this piece of code. Can some one tell me what jars and js files need to be included for this? Any online link to get them. I havent been able to download the exact dependencies needed to run this. – Arunabh Oct 19 '17 at 10:23
  • To compile it, no special jars are needed. It's all part of the standard JRE. To run it, you have to install the [Java Cryptography Extension (JCE)](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html). If you have trouble getting JCE to run, search StackOverflow and if you can't find an answer, ask a specific question about it. – Codo Oct 19 '17 at 11:03
  • I am able to write the server side code and required APIs get imported but what about the js files on the client side. I tried using this 'aes':'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes', 'enc-base64-min':'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min' but it didnt work. – Arunabh Oct 23 '17 at 12:11
  • Well, "didnt work" isn't exactly a description that would let anybody help you. Start a new question and provide serious information. – Codo Oct 23 '17 at 21:09
  • @Codo: Can you please help me with the encryption process from java. How can I encrypt the data while sending it to CryptoJS server? – Animesh Jena Mar 21 '18 at 10:57
  • @Codo First thanks for working solution. could you help for encryption? Please look this SO qtn - https://stackoverflow.com/q/49384768/3879847 – Ranjithkumar Jun 09 '18 at 10:20
  • @Codo I offered bounty also for this qtn - https://stackoverflow.com/questions/49384768/encrypt-from-android-and-decrypt-in-cryptojs – Ranjithkumar Jun 09 '18 at 10:40
  • Hi, I used ur code to decrypt in android app and it's working fine on emulator, it is also working in my device in when i install a debug build from android studio, but when uploaded app to playstore. This just wont work! I have no clue whats going on. – Prajeet Shrestha Nov 18 '18 at 07:51
  • After some testing it turns out that when I turn on minification in gradle this code doesn't seem to work! How to bypass this? – Prajeet Shrestha Nov 18 '18 at 08:23
  • Nice but I get *This rule raises an issue when a Cipher instance is created with either ECB or CBC/PKCS5Padding mode.* from SONAR – Jorge Tovar Apr 15 '19 at 22:28
  • I get a java.security.InvalidKeyException: Illegal key size error (java 8). – Agustí Sánchez May 30 '19 at 16:02
  • @Codo.. thanks for posting this solution. It worked pretty well. I have few questions as I am trying to understand AES algorithm and its implementation, particularly Crypto-JS. Could you let me know, if you will have some time to answer those ? I promise none of those will be around my project, purely based on algorithm and concepts around it. Maybe if you can write me here - himanshu.singhs@outlook.in ? then it'd be a big help. Thanks :D – Himanshu Singh Sep 12 '19 at 15:46
  • @codo Do you have same encrypt function for java – Akshay Prabhu Feb 06 '20 at 13:03
  • Siiii funciona muchas gracias – cdcaiza Jun 30 '20 at 03:02
  • nit/update: OpenJDK builds never needed the 'unlimited policy' download to do 256-bit AES, and Oracle builds starting with 8u151 and j9 in fall 2017 no longer do. – dave_thompson_085 Jul 08 '20 at 17:27
  • Can anyone explain why 'salt' is being extracted from 8th to 16th byte, and cipher from 16th? Where are first 8 bytes going? IV is 16 bytes. These bytes also seem to be always the same for the same input.... Oooh ooookaaaay, they're base64 for "Salted_" – TEH EMPRAH Jan 26 '22 at 14:33
  • @TEHEMPRAH CryptoJS follows an OpenSSL format, which always starts with the characters "Salted__". – Codo Jan 26 '22 at 14:44
3

When encrypting on one system and decrypting on another you are at the mercy of system defaults. If any system defaults do not match (and they often don't) then your decryption will fail.

Everything has to be byte for byte the same on both sides. Effectively that means specifying everything on both sides rather than relying on defaults. You can only use defaults if you are using the same system at both ends. Even then, it is better to specify exactly.

Key, IV, encryption mode, padding and string to bytes conversion all need to be the same at both ends. It is especially worth checking that the key bytes are the same. If you are using a Key Derivation Function (KDF) to generate your key, then all the parameters for that need to be the same, and hence specified exactly.

Your "Invalid AES key length" may well indicate a problem with generating your key. You use getBytes(). That is probably an error. You need to specify what sort of bytes you are getting: ANSI, UTF-8, EBCDIC, whatever. The default assumption for the string to byte conversion is the likely cause of this problem. Specify the conversion to be used explicitly at both ends. That way you can be sure that they match.

Crypto is designed to fail if the parameters do not match exactly for encryption and decryption. For example, even a one bit difference in the key will cause it to fail.

rossum
  • 15,344
  • 1
  • 24
  • 38
  • can you please help us, we have to use AES 256 in ionic 3 front end side and decrypt at java layer level. the encrypted data not able to decrypt. https://stackoverflow.com/questions/63811173/ionic-v3-aes-256-algorithm-to-using-encrypted-not-able-to-decrypt-in-java-aes-gc – Developer KE Sep 09 '20 at 12:34