Android 4.2 Broke My Encrypt/Decrypt Code and the Provided Solutions Don't Work

Android 4.2 broke my AES encrypt/decrypt code

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.

Encryption error on Android 4.2

From the Android Jellybean page:

Modified the default implementations of SecureRandom and Cipher.RSA to use OpenSSL

They changed the default provider for SecureRandom to use OpenSSL instead of the previous Crypto provider.

The following code will produce two different outputs on pre-Android 4.2 and Android 4.2:

SecureRandom rand = SecureRandom.getInstance("SHA1PRNG");
Log.i(TAG, "rand.getProvider(): " + rand.getProvider().getName());

On pre-4.2 devices:

rand.getProvider: Crypto

On 4.2 devices:

rand.getProvider: AndroidOpenSSL

Fortunately, it's easy to revert to the old behavior:

SecureRandom sr = SecureRandom.getInstance( "SHA1PRNG", "Crypto" );

To be sure, it's dangerous to be calling SecureRandom.setSeed at all in light of the Javadocs which state:

Seeding SecureRandom may be insecure

A seed is an array of bytes used to bootstrap random number generation. To produce cryptographically secure random numbers, both the seed and the algorithm must be secure.

By default, instances of this class will generate an initial seed using an internal entropy source, such as /dev/urandom. This seed is unpredictable and appropriate for secure use.

You may alternatively specify the initial seed explicitly with the seeded constructor or by calling setSeed(byte[]) before any random numbers have been generated. Specifying a fixed seed will cause the instance to return a predictable sequence of numbers. This may be useful for testing but it is not appropriate for secure use.

However, for writing unit tests, as you are doing, using setSeed may be okay.

Easy way to Encrypt/Decrypt string in Android

You can use Cipher for this.

This class provides the functionality of a cryptographic cipher for encryption and decryption. It forms the core of the Java Cryptographic Extension (JCE) framework.

Sample of encryption and decryption:

public static SecretKey generateKey() 
throws NoSuchAlgorithmException, InvalidKeySpecException
{
return secret = new SecretKeySpec(password.getBytes(), "AES");
}

public static byte[] encryptMsg(String message, SecretKey secret)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException
{
/* Encrypt the message. */
Cipher cipher = null;
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] cipherText = cipher.doFinal(message.getBytes("UTF-8"));
return cipherText;
}

public static String decryptMsg(byte[] cipherText, SecretKey secret)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidParameterSpecException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException
{
/* Decrypt the message, given derived encContentValues and initialization vector. */
Cipher cipher = null;
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret);
String decryptString = new String(cipher.doFinal(cipherText), "UTF-8");
return decryptString;
}

To encrypt:

SecretKey secret = generateKey();
EncUtil.encryptMsg(String toEncrypt, secret))

To decrypt:

EncUtil.decryptMsg(byte[] toDecrypt, secret))

android - supported algorithms bug?

I figured out how to guarantee the existence of the algorithms that I need. First I downloaded Spongey Castle and added that to my build path

I added SC as a Provider using this code

static {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
}

After doing so I still had the same error as before so then changing my code to

static void setKey(byte[] keybytes, byte[] iv) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchProviderException
{
/**
* crypto is specifically stated here because without using AndroidOpenSSL for the SHA1PRNG breaks on some phones,
* PRNGFixes.apply() should be called if using this
* https://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html
*/
random = SecureRandom.getInstance("SHA1PRNG", "Crypto");
key = new SecretKeySpec(keybytes, "AES");
ivspec = new IvParameterSpec(iv);
encryptcipher = Cipher.getInstance("AES/CFB/NoPadding", "SC");
encryptcipher.init(Cipher.ENCRYPT_MODE, key, ivspec, random);

decryptcipher = Cipher.getInstance("AES/CFB/NoPadding", "SC");
decryptcipher.init(Cipher.DECRYPT_MODE, key, ivspec, random);
}

fixed all of my problems, but then there is the security issue of using Crypto so I downloaded PRNGFixes found at https://android-developers.blogspot.com/2013/08/some-securerandom-thoughts.html and called apply elsewhere in the application before I used the crypto library

Java, error when decoding AES-256

You're doing two things wrong:

  • Generating a key from a password by using the key to seed a PRNG is a bad idea. Use password-based-encryption instead. Java has an implementation of PKCS#5 that will generate a key from a password.

  • You need to use a new strong-random IV for each encryption:

    • When you encrypt, don't specify an IV in cipher.init(). A new one will be generated for you.
    • encrypt() needs to serialise both the IV (cipher.getIV()) and the ciphertext into a byte array.
    • decrypt(): separate the IV from the ciphertext, build an IvParameterSpec from it and feed into cipher.init() as you currently do.

AES cipher text is different

For anyone who is referencing this thread this is the change I did to my code. Now it works well in Android and server environment.

Answer is taken from,

Android 4.2 broke my encrypt/decrypt code and the provided solutions don't work

Thanks @kroot

    /* Store these things on disk used to derive key later: */
int iterationCount = 1000;
int saltLength = 32; // bytes; should be the same size as the output
// (256 / 8 = 32)
int keyLength = 256; // 256-bits for AES-256, 128-bits for AES-128, etc
byte[] salt = new byte[saltLength]; // Should be of saltLength

/* When first creating the key, obtain a salt with this: */
SecureRandom random = new SecureRandom();
random.nextBytes(salt);

/* Use this to derive the key from the password: */
KeySpec keySpec = new PBEKeySpec(new String(key,
Constants.CHAR_ENCODING).toCharArray(), key, iterationCount,
keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBEWithSHA256And256BitAES-CBC-BC");
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");

return secretKey.getEncoded();


Related Topics



Leave a reply



Submit