Reputation: 3037
I'm using a custom subclass of SharedPreferences to encrypt my saved settings in the app, similar to what's being done in the second response here: What is the most appropriate way to store user settings in Android application
The number of preferences I have to save is growing. Before I was just using a custom view to update these preferences but that is going to become cumbersome and I want to use PreferenceActivity or PreferenceFragment instead. Problem is, it does not seem that there is a way to have either of those classes access my data using my subclass, meaning that the data it pulls from the default preferences file is going to be gibberish as it wasn't decrypted.
I've found that some people have created custom implementations of Preference that encrypt the data there, but I'd prefer not to do that as the data is already being encrypted/decrypted in my SharedPreferences subclass and I'd like to keep it that way. I've also been looking over the source code of PreferenceActivity and PreferenceManager and I'm not sure the best way to approach this.
Has anyone else had any luck accomplishing something like this and have any suggestions as to where I might start?
Upvotes: 16
Views: 1839
Reputation: 3836
I think by keeping your encryption in the SharedPrefs subclass that you already have, you limit the modularity and the separation of concerns.
So I would suggest reconsidering sub-classing the preference classes themselves (such as CheckBoxPreference) and perform your calculation there.
Ideally you could also use some type of composition or a static utility so that while you might have to subclass each type of preference you use, you can use a single place to perform the encryption/decryption calculations. This would also allow you more flexibility in the future if you need to encrypt or decrypt some other data or if the API changes.
For sub-classing maybe you could do this:
So for example:
class ListPreferenceCrypt extends ListPreference
{
ListPreferenceCrypt (Context context, AttributeSet attrs) {
super ( context, attrs );
}
ListPreferenceCrypt (Context context) {
super ( context );
}
@Override
public void setValue( String value )
{
//encrypt value
String encryptedVal = MyCryptUtil.encrypt(value);
super.setValue ( encryptedVal );
}
@Override
public String getValue( String key )
{
//decrypt value
String decryptedValue = MyCryptUtil.decrypt(super.getValue ( key ));
return decryptedValue;
}
}
NOTE the above is psuedo-code, there would be different methods to override
And your XML might look like this:
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/inline_preferences">
<com.example.myprefs.ListPreferenceCrypt
android:key="listcrypt_preference"
android:title="@string/title_listcrypt_preference"
android:summary="@string/summary_listcrypt_preference" />
</PreferenceCategory>
</PreferenceScreen>
EDIT
Caveats/Decompiling
As I thought about this more, I realized one of the caveats is that this method is not particularly difficult to bypass when decompiling an APK. This does give the full class names of overriden classes in the layouts (though that can be avoided by not using XML)
However, I don't think this is significantly less secure than sub-classing SharedPreferences
. That too, is susceptible to decompiling. Ultimately, if you want stronger security, you should consider alternative storage methods. Perhaps OAuth or the AccountManager as suggested in your linked post.
Upvotes: 2
Reputation: 6610
How about this:
Now that you've setup the keys let me explain:
Yes, potentially one could peek into the .SO and find the byte array ergo your key. But with the cyphered key2 being base64 encoded, he would need to decode it and reverse the encryption with the said key to extract key2 bytes. So far this only involves dissassembling the app.
With these two steps disassembling the app is no longer the only thing required, as it's now required to also take control of your runtime to get to the crypted data. Which you have to say is pretty sufficient for a stored password.
Then you could simply store that into SharedPreferences :) That way if your SharedPreferences get compromised, the data is still locked away. I don't think subclassing it is really the right approach but since you already wrote your class - oh well.
Here's some code to further illustrate what I mean
//use to encrypt key
public static byte[] encryptA(byte[] value) throws GeneralSecurityException, IOException
{
SecretKeySpec sks = getSecretKeySpec(true);
System.err.println("encrypt():\t" + sks.toString());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sks, cipher.getParameters());
byte[] encrypted = cipher.doFinal(value);
return encrypted;
}
//use to encrypt data
public static byte[] encrypt2(byte[] value) throws GeneralSecurityException, IOException
{
SecretKeySpec key1 = getSecretKeySpec(true);
System.err.println("encrypt():\t" + key1.toString());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key1, cipher.getParameters());
byte[] encrypted = cipher.doFinal(value);
SecretKeySpec key2 = getSecretKeySpec(false);
System.err.println("encrypt():\t" + key2.toString());
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key2, new IvParameterSpec(getIV()));
byte[] encrypted2 = cipher.doFinal(encrypted);
return encrypted2;//Base64Coder.encode(encrypted2);
}
//use to decrypt data
public static byte[] decrypt2(byte[] message, boolean A) throws GeneralSecurityException, IOException
{
SecretKeySpec key1 = getSecretKeySpec(false);
System.err.println("decrypt():\t" + key1.toString());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key1, new IvParameterSpec(getIV()));
byte[] decrypted = cipher.doFinal(message);
SecretKeySpec key2 = getSecretKeySpec(true);
System.err.println("decrypt():\t" + key2.toString());
cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key2);
byte[] decrypted2 = cipher.doFinal(decrypted);
return decrypted2;
}
//use to decrypt key
public static byte[] decryptKey(String message, byte[] key) throws GeneralSecurityException
{
SecretKeySpec sks = new SecretKeySpec(key, ALGORITHM);
System.err.println("decryptKey()");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sks);
byte[] decrypted = cipher.doFinal(Base64Coder.decode(message));
return decrypted;
}
//method for fetching keys
private static SecretKeySpec getSecretKeySpec(boolean fromSO) throws NoSuchAlgorithmException, IOException, GeneralSecurityException
{
return new SecretKeySpec(fromSO ? getKeyBytesFromSO() : getKeyBytesFromAssets(), "AES");
}
What do you think?
I realize it might be off topic since you're asking about using your own SharedPreferences but I'm giving you a working solution to the problem of storing sensitive data :)
Upvotes: 1