Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android 4.2 broke my AES encrypt/decrypt code

Tags:

android

aes

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

like image 916
Klaus Villaca Avatar asked Nov 15 '12 00:11

Klaus Villaca


2 Answers

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.

like image 57
Klaus Villaca Avatar answered Oct 14 '22 22:10

Klaus Villaca


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").

like image 30
Paolo Brandoli Avatar answered Oct 14 '22 23:10

Paolo Brandoli