14

It's my first time asking for help in here, my department (a Government), have published some app on the market (Google Play), and the encryption and description was working really well up to yesterday when I got the Jelly Bean 4.2 on my Nexus. The encrypt works fine, it's in fact encrypt the information to be stored. Though when decrypt it, I'm getting an exception exactly like this : pad block corrupted. I've checked the string and it's consistent with it on others devices (using the same key for test purposes), meaning it's exactly the same. The problem is that we need keep the back compatibility with previous versions, meaning that if I change something in the code, it's should be able to read the old encrypted information. The encrypted information it's stored on SQLite, due that I need encode it to Base64. The exception happen on this line byte[] decrypted = cipher.doFinal(encrypted);

Here is my class:

import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import android.util.Base64;

public class EncodeDecodeAES {

    private final static String HEX = "0123456789ABCDEF";

    public static String encrypt(String seed, String cleartext) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] result = encrypt(rawKey, cleartext.getBytes());
        String fromHex = toHex(result);
        String base64 = new String(Base64.encodeToString(fromHex.getBytes(), 0));
        return base64;
    }


    public static String decrypt(String seed, String encrypted) throws Exception {
        String base64 = new String(Base64.decode(encrypted, 0));
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] enc = toByte(base64);
        byte[] result = decrypt(rawKey, enc);
        return new String(result);
    }


    public static byte[] encryptBytes(String seed, byte[] cleartext) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] result = encrypt(rawKey, cleartext);
        return result;
    }


    public static byte[] decryptBytes(String seed, byte[] encrypted) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] result = decrypt(rawKey, encrypted);
        return result;
    }



    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(seed);
        try {
            kgen.init(256, sr);
        } catch (Exception e) {
    //      Log.w(LOG, "This device doesn't suppor 256bits, trying 192bits.");
            try {
                kgen.init(192, sr);
            } catch (Exception e1) {
    //          Log.w(LOG, "This device doesn't suppor 192bits, trying 128bits.");
                kgen.init(128, sr);
            }
        }
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }


    private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }


    private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }


    public static String toHex(String txt) {
        return toHex(txt.getBytes());
    }


    public static String fromHex(String hex) {
        return new String(toByte(hex));
    }


    public static byte[] toByte(String hexString) {
        int len = hexString.length() / 2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
        return result;
    }


    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }


    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
    }

}

I would like to know (if somebody help me), what 'm I doing wrong with this code, or if it's a issue with Android 4.2 and if it's a issue with 4.2 if has any workaround?

Thank you

Cat
  • 66,919
  • 24
  • 133
  • 141
Klaus Villaca
  • 587
  • 1
  • 6
  • 11
  • I'm actually having a similar problem; AES'd information stored on my phone is no longer correctly decrypted in 4.2. Seems they changed something in the library; strictly not impressed with that move. – Cat Nov 15 '12 at 00:46
  • https://android.googlesource.com/platform/libcore/+/72e44404c32a98e7675a6e7cfbf856adb499a434%5E!/#F3 seems to be a recent change that seems to touch AES and crypto, maybe you need to be more specific about the encryption mode than `"AES"` now. (Idk nothing about those modes but from looking at the source there are mentions of things like `"AES/ECB/NoPadding"` etc) – zapl Nov 15 '12 at 01:22
  • Try `"AES/CBC/NoPadding"`, that looks like it should be the default. I think there is a bug in that code, in the new `provideCipherPaddings` method they add the available paddings to the list of modes instead to the paddings. – zapl Nov 15 '12 at 01:41
  • Hi, thank you for all prompt answers, after implement "AES/CBC/NoPadding" on my code, I'm getting no IV set when one expected this is happening when decrypting. – Klaus Villaca Nov 15 '12 at 03:03
  • Update: I have tested all options available to decrypt a cipher, without any success ("CBC", "CFB", "CTR", "CTS", "ECB", "OFB","NoPadding", "PKCS5Padding"), did try even making combinations, though it's doesn't work at all. Thank you for you all, I'll give a try Spongy Caste jar. – Klaus Villaca Nov 15 '12 at 03:54
  • 3
    Related to this one? http://stackoverflow.com/questions/13383006/encryption-error-on-android-4-2 – Christer Nordvik Nov 15 '12 at 12:33

2 Answers2

16

WARNING This answer uses SecureRandom for key derivation, which is contrary to its purpose. SecureRandom is a random number generator and is not guaranteed to produce consistent output between platforms (which is what caused the problem in the question). The proper mechanism for key derivation is SecretKeyFactory. This nelenkov's blog post has a good write-up on this issue. This answer provides a solution for cases when you are constrained by backwards compatibility requirement; however, you should migrate to a correct implementation as soon as possible.


Ok, today with a little more time to do some research (and remove my old post, that actually wasn't work, sorry) I got one answer that's working fine, I actually did test it on Android 2.3.6, 2.3.7 (that's basically the same), 4.0.4 and 4.2 and it has worked. I did some research on those links :

Encryption error on Android 4.2,

BouncyCastle AES error when upgrading to 1.45,

http://en.wikipedia.org/wiki/Padding_(cryptography)

Then I got in this solution thanks to the content on those links above. Here is my class (and now working fine):

    package au.gov.dhsJobSeeker.main.readwriteprefssettings.util;

    import java.security.SecureRandom;

    import javax.crypto.Cipher;
    import javax.crypto.KeyGenerator;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;

    import android.util.Base64;

    public class EncodeDecodeAES {

private final static String HEX = "0123456789ABCDEF";
private final static int JELLY_BEAN_4_2 = 17;
private final static byte[] key = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };


// static {
// Security.addProvider(new BouncyCastleProvider());
// }

public static String encrypt(String seed, String cleartext) throws Exception {
    byte[] rawKey = getRawKey(seed.getBytes());
    byte[] result = encrypt(rawKey, cleartext.getBytes());
    String fromHex = toHex(result);
    String base64 = new String(Base64.encodeToString(fromHex.getBytes(), 0));
    return base64;
}


public static String decrypt(String seed, String encrypted) throws Exception {
    byte[] seedByte = seed.getBytes();
    System.arraycopy(seedByte, 0, key, 0, ((seedByte.length < 16) ? seedByte.length : 16));
    String base64 = new String(Base64.decode(encrypted, 0));
    byte[] rawKey = getRawKey(seedByte);
    byte[] enc = toByte(base64);
    byte[] result = decrypt(rawKey, enc);
    return new String(result);
}


public static byte[] encryptBytes(String seed, byte[] cleartext) throws Exception {
    byte[] rawKey = getRawKey(seed.getBytes());
    byte[] result = encrypt(rawKey, cleartext);
    return result;
}


public static byte[] decryptBytes(String seed, byte[] encrypted) throws Exception {
    byte[] rawKey = getRawKey(seed.getBytes());
    byte[] result = decrypt(rawKey, encrypted);
    return result;
}


private static byte[] getRawKey(byte[] seed) throws Exception {
    KeyGenerator kgen = KeyGenerator.getInstance("AES"); // , "SC");
    SecureRandom sr = null;
    if (android.os.Build.VERSION.SDK_INT >= JELLY_BEAN_4_2) {
        sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
    } else {
        sr = SecureRandom.getInstance("SHA1PRNG");
    }
    sr.setSeed(seed);
    try {
        kgen.init(256, sr);
        // kgen.init(128, sr);
    } catch (Exception e) {
        // Log.w(LOG, "This device doesn't suppor 256bits, trying 192bits.");
        try {
            kgen.init(192, sr);
        } catch (Exception e1) {
            // Log.w(LOG, "This device doesn't suppor 192bits, trying 128bits.");
            kgen.init(128, sr);
        }
    }
    SecretKey skey = kgen.generateKey();
    byte[] raw = skey.getEncoded();
    return raw;
}


private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
    SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
    Cipher cipher = Cipher.getInstance("AES"); // /ECB/PKCS7Padding", "SC");
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
    byte[] encrypted = cipher.doFinal(clear);
    return encrypted;
}


private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
    SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
    Cipher cipher = Cipher.getInstance("AES"); // /ECB/PKCS7Padding", "SC");
    cipher.init(Cipher.DECRYPT_MODE, skeySpec);
    byte[] decrypted = cipher.doFinal(encrypted);
    return decrypted;
}


public static String toHex(String txt) {
    return toHex(txt.getBytes());
}


public static String fromHex(String hex) {
    return new String(toByte(hex));
}


public static byte[] toByte(String hexString) {
    int len = hexString.length() / 2;
    byte[] result = new byte[len];
    for (int i = 0; i < len; i++)
        result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
    return result;
}


public static String toHex(byte[] buf) {
    if (buf == null) return "";
    StringBuffer result = new StringBuffer(2 * buf.length);
    for (int i = 0; i < buf.length; i++) {
        appendHex(result, buf[i]);
    }
    return result.toString();
}


private static void appendHex(StringBuffer sb, byte b) {
    sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}

    }

However the PBrando answer(above, also works, due that I marked it as solution.), though as I was looking for a way to keep a similar app file size with it's now, I've opted to use this approach. Because I don't need to import external Jars. I did put the entire class for just in case any of you is having the same issue, and want to just copy ans paste it.

Community
  • 1
  • 1
Klaus Villaca
  • 587
  • 1
  • 6
  • 11
  • 1
    Have you tried to decrypt in Android 4.2 data encrypted in Android <= 4.2? I have the same code as you but I am getting the "pad block corrupted" exception – pandre Nov 16 '12 at 18:54
  • Take a look on this link : http://kvandroidapp.blogspot.com.au/ , there I show you an example. – Klaus Villaca Nov 17 '12 at 09:00
  • do you think it has to do with the seed length? I am using a short seed (4 chars long) – pandre Nov 17 '12 at 15:09
  • Any idea on this: http://stackoverflow.com/questions/13433529/android-4-2-broke-my-encrypt-decrypt-code-and-the-provided-solutions-dont-work ? – pandre Nov 17 '12 at 19:02
  • 1
    Hi Pandre I did put an answer there, I had this sort of problem with one of my apps (Data Safe) and to fix this I did exact what di put there. – Klaus Villaca Nov 18 '12 at 12:26
  • 2
    This answer is ***absolutely incorrect***, you should never ever use `SecureRandom` as a key derivation function. See the answers and comments on [this question](http://stackoverflow.com/questions/13433529/android-4-2-broke-my-encrypt-decrypt-code-and-the-provided-solutions-dont-work/13438590#13438590) – Maarten Bodewes Feb 15 '13 at 12:26
  • Quote from the answer "If you accidentally used some other method such as SecureRandom to derive keys, you can recover. Only use this to migrate away to a real Password-Based Key Derivation Function! It may not work at all later.", usually those SecureRandom are not new implementations and to keep everything working as before, it's has to be used, other else I should have to create a migrate tool within. Btw it's not absolutely incorrect as you said. – Klaus Villaca Feb 20 '13 at 00:28
  • Gives "pad block corrupted" on 4.2.2 – WindRider Apr 20 '13 at 22:28
  • Thanks for your research and it really helped me. Am not sure about this encryption decryption concept. However I am gettting the following error when I try to decrypt(which was encrpted using code in question) the encrypted text using the old code. Hence I need to ask my users to uninstall and reinstall the app which is the only way. Any help would be appreciated. W/System.err(8151): java.lang.NumberFormatException: Invalid int: "��" 05-05 01:15:47.803: W/System.err(8151): at java.lang.Integer.invalidInt(Integer.java:138) – Sathesh May 05 '13 at 01:25
  • I've added a better explanation on how to generate AES keys [here](http://stackoverflow.com/a/24125677/589259) – Maarten Bodewes Jul 23 '14 at 21:18
1

You could try to use the SpongyCastle library. It is the BouncyCastle patched to compile on Android.

Since it is compatible with BouncyCastle (only the package name and the service provider are different, "SC" instead of "BC"), and Android uses a subset of BouncyCastle, integrating SpongyCastle in your code should be a trivial task.

You can find SpongyCastle here: http://rtyley.github.com/spongycastle/

Take care of registering SpongyCastle as explained in their website:

static {
    Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
}

When you get instances of crypto objects, specify also the provider ("SC").

Paolo Brandoli
  • 4,681
  • 26
  • 38
  • If up tomorrow morning I couldn't find anything then there will be no other option but put the SpongyCastle.jar, the issue it that the app have already 6.2MB (apk), and over 11MB installed. However thank you for the answer, I'll give it a try. – Klaus Villaca Nov 15 '12 at 03:05
  • Using Spongy Castle I'm still getting "no IV set when one expected", and I'm using "Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");" to decrypt it. – Klaus Villaca Nov 15 '12 at 04:52
  • The default for "AES" (which you were using before) is "AES/ECB/PKCS5Padding", which doesn't require the initialization vector – Paolo Brandoli Nov 15 '12 at 07:50
  • It's what I thought, though checking those objects I got it "AES/CBC/PKCS7Padding" because it's getting the raw key with 256bits. As my both devices now run 4.2, at moment I can't tell you the difference with the previous Android version. Though as soon I got one running older version, will compare all object tree to see if it's have any difference. Thank you – Klaus Villaca Nov 15 '12 at 12:03
  • Guys it's me once again, I have to delete my answer, due few things, the code that I did used was different, I should have forgot to ctrl-c once again from Eclipse, even the code that I have used just worked for previous 4.2 versions, the Nexus on my desk wasn't been updated, and there is an answer in the last comment after my question. Sorry for that. – Klaus Villaca Nov 15 '12 at 23:23