6

I need to decrypt some data that I receive from the server, and the programmer who made the API directed me to this Encrypter class, to see what he used to encrypt.

Now based on that class, I found that the algorithm used is AES128 CBC, and that the string I receive is Base64 encoded and contains other data, not just the ciphertext.

Namely that if I receive the following String:

eyJpdiI6InJsSzRlU3pDZTBBUVNwMzdXMjVcL0tBPT0iLCJ2YWx1ZSI6Ik5JOENsSVVWaWk2RGNhNlwvWjJNeG94UzVkclwvMGJOREQreWUyS1UzclRMND0iLCJtYWMiOiJhZTZkYjNkNGM2ZTliNmU0ZTc0MTRiNDBmMzFlZTJhNTczZWIxMjk4N2YwMjlhODA1NTIyMDEzODljNDY2OTk2In0

after base64 decoding I get:

{"iv":"rlK4eSzCe0AQSp37W25\/KA==","value":"NI8ClIUVii6Dca6\/Z2MxoxS5dr\/0bNDD+ye2KU3rTL4=","mac":"ae6db3d4c6e9b6e4e7414b40f31ee2a573eb12987f029a80552201389c466996"}

Based on line 99 of Encrypter class ( iv = base64_decode($payload['iv']); ), I performed another base64 decode on the iv and the value , and got an iv of length 16. Those I passed as parameters to the function below:

    public static String decrypt(String iv, String encryptedData) throws Exception {
    byte[] keyValue = "zy2dEd1pKG5i3WuWbvOBolFQR84AYbvN".getBytes();
    Key key = new SecretKeySpec(keyValue, "AES");        
    Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding");
    c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv.getBytes()));
    byte[] decordedValue = Base64.decode(encryptedData.getBytes(), Base64.DEFAULT);
    byte[] decValue = c.doFinal(decordedValue);
    return new String(decValue);
}

But I'm getting the following error:

10-06 19:13:33.601 12895-12895/? W/System.err: java.security.InvalidAlgorithmParameterException: expected IV length of 16
10-06 19:13:33.601 12895-12895/? W/System.err:     at com.android.org.conscrypt.OpenSSLCipher.engineInitInternal(OpenSSLCipher.java:281)
10-06 19:13:33.601 12895-12895/? W/System.err:     at com.android.org.conscrypt.OpenSSLCipher.engineInit(OpenSSLCipher.java:323)
10-06 19:13:33.601 12895-12895/? W/System.err:     at javax.crypto.Cipher.init(Cipher.java:751)
10-06 19:13:33.601 12895-12895/? W/System.err:     at javax.crypto.Cipher.init(Cipher.java:701)
10-06 19:13:33.601 12895-12895/? W/System.err:     at com.example.kushtrim.testproject.MainActivity.decrypt(MainActivity.java:62)
10-06 19:13:33.601 12895-12895/? W/System.err:     at com.example.kushtrim.testproject.MainActivity.onCreate(MainActivity.java:45)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.Activity.performCreate(Activity.java:5990)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2278)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.ActivityThread.access$800(ActivityThread.java:151)
10-06 19:13:33.601 12895-12895/? W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
10-06 19:13:33.602 12895-12895/? W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:102)
10-06 19:13:33.602 12895-12895/? W/System.err:     at android.os.Looper.loop(Looper.java:135)
10-06 19:13:33.602 12895-12895/? W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5254)
10-06 19:13:33.602 12895-12895/? W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
10-06 19:13:33.602 12895-12895/? W/System.err:     at java.lang.reflect.Method.invoke(Method.java:372)
10-06 19:13:33.602 12895-12895/? W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
10-06 19:13:33.602 12895-12895/? W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

Note: The String iv has length of 16, but iv.getBytes() returns an array of length 26.

Could someone point me to where I went wrong, and how do I fix it. Thanks/

EDIT
After the comment, I made some changes, that resolved the above error:
Before I was base64 decoding iv, converting the bytes to String, then passing that String to the decrypt method, which in return called the getBytes() on it. Somehow this made the byte array have a length of 26.
Sending the byte array I obtained after base64 decoding to the decrypt method fixed the problem.
Now the method is as follows:

public static String decrypt(byte[] iv, String encryptedData) throws Exception {
    byte[] keyValue = "zy2dEd1pKG5i3WuWbvOBolFQR84AYbvN".getBytes();
    Key key = new SecretKeySpec(keyValue, "AES");
    Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding");
    c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
    byte[] decordedValue = Base64.decode(encryptedData.getBytes(), Base64.DEFAULT);
    byte[] decValue = c.doFinal(decordedValue);
    return new String(decValue);
}

Now I have another weird problem:
The text I encrypted on the first place was KushtrimPacaj , but the decrypted text is s:13:"KushtrimPacaj"; . Where is that other part coming from ? 13 perhaps represents the length of KushtrimPacaj ?

Edit
Here's the working code, in case anyone needs it :
https://gist.github.com/KushtrimPacaj/43a383ab419fc222f80e

Community
  • 1
  • 1
Rick Sanchez
  • 4,528
  • 2
  • 27
  • 53
  • Please provide a full example. If `iv` is a String, what's its value? Have you forgot to decode it? I see no reason the length would be 26. Keep in mind that you cannot pass binary/unprintable data as a String. You would need to use a byte array. – Artjom B. Oct 06 '15 at 17:29
  • @ArtjomB. Thanks for the comment, you gave me an idea that led to the length problem. Though now I have another weird one ( see the edited question ). Any idea on how to fix it ? – Rick Sanchez Oct 06 '15 at 18:08

1 Answers1

2

You can see in the padAndMcrypt() function, that the given $value is serialized using PHP's serialize() function. You can re-implement the unserialize() function in Java or you can split the byte array yourself if you're always encrypting strings in PHP.

int firstQuoteIndex = 0;
while(decValue[firstQuoteIndex] != (byte)'"') firstQuoteIndex++;
return new String(Arrays.copyOfRange(decValue, firstQuoteIndex + 1, decValue.length-2));

Full code:

public static String decrypt(byte[] keyValue, String ivValue, String encryptedData) throws Exception {
    Key key = new SecretKeySpec(keyValue, "AES");
    byte[] iv = Base64.decode(ivValue.getBytes("UTF-8"), Base64.DEFAULT);
    byte[] decodedValue = Base64.decode(encryptedData.getBytes("UTF-8"), Base64.DEFAULT);

    Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding"); // or PKCS5Padding
    c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
    byte[] decValue = c.doFinal(decodedValue);

    int firstQuoteIndex = 0;
    while(decValue[firstQuoteIndex] != (byte)'"') firstQuoteIndex++;
    return new String(Arrays.copyOfRange(decValue, firstQuoteIndex + 1, decValue.length-2));
}

Verifying the MAC is always a good idea, because it prevents some attacks such as the padding oracle attack. It is also a very good way to detect general modifications of ciphertexts.

Full code with MAC verification:

public static String decrypt(byte[] keyValue, String ivValue, String encryptedData, String macValue) throws Exception {
    Key key = new SecretKeySpec(keyValue, "AES");
    byte[] iv = Base64.decode(ivValue.getBytes("UTF-8"), Base64.DEFAULT);
    byte[] decodedValue = Base64.decode(encryptedData.getBytes("UTF-8"), Base64.DEFAULT);

    SecretKeySpec macKey = new SecretKeySpec(keyValue, "HmacSHA256");
    Mac hmacSha256 = Mac.getInstance("HmacSHA256");
    hmacSha256.init(macKey);
    hmacSha256.update(ivValue.getBytes("UTF-8"));
    byte[] calcMac = hmacSha256.doFinal(encryptedData.getBytes("UTF-8"));
    byte[] mac = Hex.decodeHex(macValue.toCharArray());
    if (!secureEquals(calcMac, mac))
        return null; // or throw exception

    Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding"); // or PKCS5Padding
    c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
    byte[] decValue = c.doFinal(decodedValue);

    int firstQuoteIndex = 0;
    while(decValue[firstQuoteIndex] != (byte)'"') firstQuoteIndex++;
    return new String(Arrays.copyOfRange(decValue, firstQuoteIndex + 1, decValue.length-2));
}

/* Constant-time compare to prevent timing attacks on invalid authentication tags. */
public static boolean secureEquals(final byte[] known, final byte[] user) {
    int knownLen = known.length;
    int userLen = user.length;

    int result = knownLen ^ userLen;
    for (int i = 0; i < knownLen; i++) {
        result |= known[i] ^ user[i % userLen];
    }
    return result == 0;
}
Community
  • 1
  • 1
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
  • Thanks man. I knew how to split the String but didn't dare to, because I didn't know why it was happening, and whether it would always follow the same pattern. Also could you edit the answer, and make the second parameter in copyOfRange : "firstQuoteIndex + 1", since the start index is inclusive. Stackoverflow won't let me make an edit that it's less than 6 characters. – Rick Sanchez Oct 06 '15 at 18:42
  • I've added MAC verification. Can you check whether it works as expected? – Artjom B. Oct 06 '15 at 19:49
  • `// TODO: use time-constant compare` Would you rather make a Github project for this? – Scott Arciszewski Oct 06 '15 at 21:28
  • @ScottArciszewski Not at this time, because ... I don't have the time to do this properly. – Artjom B. Oct 06 '15 at 21:45
  • Yeah, it works as expected, with one minor change: I had to pass keyValue as first parameter to SecretKeySpec. Thanks :). Could you also check the MAC generation in the encrypt method ? I used the same IV, plaintext and key, but the MAC generated isn't the same that I received on the first place ! – Rick Sanchez Oct 07 '15 at 13:32
  • @Kushtrim The initial question is concerned with decryption which is already answered. I have no intention of blowing up the answer further. I would suggest that you ask a new question, but here are things to consider: (1) the IV has to be randomly generated and only encoded to Base64 after it was used for encryption and MAC. (2) There is probably some difference between the byte array length and String length when you do `plaintext.length()`. – Artjom B. Oct 07 '15 at 13:53
  • https://github.com/reza-khalafi/LaravelCrypt/blob/master/laravelEncrypt.php – reza_khalafi Jan 14 '17 at 10:18
  • Everybody knows how to implement this code for Objective C? – reza_khalafi Jan 14 '17 at 10:27
  • @reza_khalafi If you have a new question, then do so. Comments of an unrelated Q&A are not the right place to ask for that. – Artjom B. Jan 14 '17 at 10:45
  • Please check this : http://stackoverflow.com/questions/41542851/how-to-implement-laravel-function-cryptencrypt-in-objective-c @ArtjomB. – reza_khalafi Jan 14 '17 at 11:06
  • @ArtjomB. Can you please send the java encryption method, as I can find here decrypt but not able to find encrypt. Tried this one https://gist.github.com/KushtrimPacaj/43a383ab419fc222f80e but not working. – Vijay Sankhat May 22 '19 at 05:28
  • 1
    @VijaySankhat OK, the salary is 10000 €. You can send me 5000 € first and the second half upon completion of the contract work. – Artjom B. May 22 '19 at 18:43