Reputation: 113
I'm using this code snippet to encrypt/decrypt data in my app's database:
http://www.androidsnippets.com/encryptdecrypt-strings
It appears that the javax.crypto.KeyGenerator.generateKey() operation works differently in Android 2.3.3 OS than in other (previous?) versions. Naturally, this presents a major problem for my users when they upgrade their device from 2.2 to 2.3.3 and the app starts throwing errors decrypting the database.
Is this a known issue? Am I using the crypto library incorrectly? Anyone have any suggestions on how to address this so that data encrypted in 2.2 is able to be decrypted in 2.3.3?
I built a test app that feeds values through the encrypt function. When I run it on a 2.2 AVD, I get one result. When I run it on a 2.3.3 AVD, I get a different result.
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class main extends Activity {
TextView tvOutput;
static String out;
String TEST_STRING = "abcdefghijklmnopqrstuvwxyz";
String PASSKEY = "ThePasswordIsPassord";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tvOutput = (TextView) findViewById(R.id.tvOutput);
}
@Override
public void onResume() {
super.onResume();
out = "";
runTest();
tvOutput.setText(out);
}
private void runTest() {
out = "Test string: " + TEST_STRING + "\n";
out += "Passkey: " + PASSKEY + "\n";
try {
out += "Encrypted: " + encrypt(PASSKEY, TEST_STRING) + "\n";
} catch (Exception e) {
out += "Error: " + e.getMessage() + "\n";
e.printStackTrace();
}
}
public static String encrypt(String seed, String cleartext)
throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] result = encrypt(rawKey, cleartext.getBytes());
return toHex(result) + "\n" + "Raw Key: " + String.valueOf(rawKey)
+ "\n";
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(seed);
kgen.init(128, sr); // 192 and 256 bits may not be available
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;
}
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 final static String HEX = "0123456789ABCDEF";
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}
}
My main.xml layout looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:id="@+id/tvOutput" />
</LinearLayout>
I can't post links or images since I'm a new users, but you can decipher the URLs for the following two images if you want to see the results:
What I get from 2.2:
..and from 2.3.3:
Upvotes: 6
Views: 4033
Reputation: 113
I'd like to thank everyone that contributed to this question.
Here is what I ultimately came up with as an example for how to encrypt/decrypt using a password, that seems consistent between Android 2.2 and 2.3.3.
Main Activity:
package cc.ndl.testencryption;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class main extends Activity {
TextView tvOutput;
static String out;
String TEST_STRING = "abcdefghijklmnopqrstuvwxyz";
static String PASSKEY = "ThePasswordIsPassord";
static byte[] SALT = { 1, 2, 4, 5 };
static int ITERATIONS = 1979;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tvOutput = (TextView) findViewById(R.id.tvOutput);
}
@Override
public void onResume() {
super.onResume();
out = "";
runTest();
tvOutput.setText(out);
}
private void runTest() {
out = "Test string: " + TEST_STRING + "\n";
out += "Passkey: " + PASSKEY + "\n";
try {
Crypto crypto = new Crypto(PASSKEY);
String encryptedData = crypto.encrypt(TEST_STRING);
out += "Encrypted: " + encryptedData + "\n";
out += "Decrypted: " + crypto.decrypt(encryptedData);
} catch (Exception e) {
out += "Error: " + e.getMessage() + "\n";
e.printStackTrace();
}
}
}
Main Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:id="@+id/tvOutput" />
</LinearLayout>
Crypto Object:
package cc.ndl.testencryption;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public class Crypto {
Cipher ecipher;
Cipher dcipher;
// 8-byte Salt
byte[] salt = { 1, 2, 4, 5, 7, 8, 3, 6 };
// Iteration count
int iterationCount = 1979;
Crypto(String passPhrase) {
try {
// Create the key
KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt,
iterationCount);
SecretKey key = SecretKeyFactory.getInstance(
"PBEWITHSHA256AND128BITAES-CBC-BC").generateSecret(keySpec);
ecipher = Cipher.getInstance(key.getAlgorithm());
dcipher = Cipher.getInstance(key.getAlgorithm());
// Prepare the parameter to the ciphers
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt,
iterationCount);
// Create the ciphers
ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
} catch (Exception e) {
}
}
public String encrypt(String str) {
String rVal;
try {
// Encode the string into bytes using utf-8
byte[] utf8 = str.getBytes("UTF8");
// Encrypt
byte[] enc = ecipher.doFinal(utf8);
// Encode bytes to base64 to get a string
rVal = toHex(enc);
} catch (Exception e) {
rVal = "Error encrypting: " + e.getMessage();
}
return rVal;
}
public String decrypt(String str) {
String rVal;
try {
// Decode base64 to get bytes
byte[] dec = toByte(str);
// Decrypt
byte[] utf8 = dcipher.doFinal(dec);
// Decode using utf-8
rVal = new String(utf8, "UTF8");
} catch (Exception e) {
rVal = "Error encrypting: " + e.getMessage();
}
return rVal;
}
private 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;
}
private 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 final static String HEX = "0123456789ABCDEF";
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}
}
Upvotes: 1
Reputation: 42640
You are misusing a pseudo random number generator and it's seed as a key derivation function - this is really really bad style. The pseudo random number generator "SHA1PRNG" is not a standard like AES - therefore you never know what implementation you get. See also Is there a SHA1PRNG standard?
It makes me no wonder that you get different results. Getting a deterministic result based on a given seed is not a property you can expect from a pseudo random number functions.
If you want to derive a cryptographic key from a password please use a Key Derivation Function like PKCS #5 / PBKDF2. An implementation of PBKDF2 is AFAIR included in Bouncy Castle.
Upvotes: 5
Reputation: 3465
The answer is in this SO question: BouncyCastle AES error when upgrading to 1.45
Upvotes: 2