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
Locationsettingsrequest Dialog to Enable Gps - Onactivityresult() Skipped
How to Avoid Soft Keyboard Pushing Up My Layout
Example Communicating with Handlerthread
Creating a Preference Screen with Support (V21) Toolbar
Get Spinner Selected Items Text
How To: Define Theme (Style) Item for Custom Widget
How to Check If an Activity Is the Last One in the Activity Stack for an Application
Android Support Repo 46.0.0 with Android Studio 2.3
How to Capture an Image and Store It with the Native Android Camera
How to Set the Edittext Keyboard to Only Consist of Numbers on Android
Alphabetindexer with Custom Adapter Managed by Loadermanager
How to Send an Email with a File Attachment in Android
Can Multiple Android Application Access Same Firebase Database
Add Extra User Information with Firebase