package oak; /** * User: mlake Date: 12/16/11 Time: 4:05 PM */ import android.content.Context; import android.content.SharedPreferences; import android.provider.Settings; import java.util.Map; import java.util.Set; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; /** * Warning, this gives a false sense of security. If an attacker has enough access to acquire your * password store, then he almost certainly has enough access to acquire your source binary and * figure out your encryption key. However, it will prevent casual investigators from acquiring * passwords, and thereby may prevent undesired negative publicity. */ /** * This code originally posted by Michael Burton on StackOverflow http://stackoverflow.com/questions/785973/what-is-the-most-appropriate-way-to-store-user-settings-in-android-application/6393502#6393502 */ public abstract class ObscuredSharedPreferences implements SharedPreferences { protected static final String UTF8 = "utf-8"; protected SharedPreferences delegate; protected Context context; public ObscuredSharedPreferences(Context context, SharedPreferences delegate) { this.delegate = delegate; this.context = context; } /** * Implement this method to supply your char array with your password */ protected abstract char[] getSpecialCode(); public class Editor implements SharedPreferences.Editor { protected SharedPreferences.Editor delegate; public Editor() { this.delegate = ObscuredSharedPreferences.this.delegate.edit(); } @Override public Editor putBoolean(String key, boolean value) { delegate.putString(key, encrypt(Boolean.toString(value))); return this; } @Override public Editor putFloat(String key, float value) { delegate.putString(key, encrypt(Float.toString(value))); return this; } @Override public Editor putInt(String key, int value) { delegate.putString(key, encrypt(Integer.toString(value))); return this; } @Override public Editor putLong(String key, long value) { delegate.putString(key, encrypt(Long.toString(value))); return this; } @Override public Editor putString(String key, String value) { delegate.putString(key, encrypt(value)); return this; } // @Override public SharedPreferences.Editor putStringSet(String s, Set<String> strings) { return null; //To change body of implemented methods use File | Settings | File Templates. } // commented out for 2.1 compatibility // @Override // public void apply() { // delegate.apply(); // } @Override public Editor clear() { delegate.clear(); return this; } @Override public boolean commit() { return delegate.commit(); } // @Override public void apply() { //To change body of implemented methods use File | Settings | File Templates. } @Override public Editor remove(String s) { delegate.remove(s); return this; } } public Editor edit() { return new Editor(); } @Override public Map<String, ?> getAll() { throw new UnsupportedOperationException(); // left as an exercise to the reader } @Override public boolean getBoolean(String key, boolean defValue) { final String v = delegate.getString(key, null); return v != null ? Boolean.parseBoolean(decrypt(v)) : defValue; } @Override public float getFloat(String key, float defValue) { final String v = delegate.getString(key, null); return v != null ? Float.parseFloat(decrypt(v)) : defValue; } @Override public int getInt(String key, int defValue) { final String v = delegate.getString(key, null); return v != null ? Integer.parseInt(decrypt(v)) : defValue; } @Override public long getLong(String key, long defValue) { final String v = delegate.getString(key, null); return v != null ? Long.parseLong(decrypt(v)) : defValue; } @Override public String getString(String key, String defValue) { final String v = delegate.getString(key, null); return v != null ? decrypt(v) : defValue; } @Override public boolean contains(String s) { return delegate.contains(s); } @Override public void registerOnSharedPreferenceChangeListener( OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { delegate.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener); } @Override public void unregisterOnSharedPreferenceChangeListener( OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { delegate.unregisterOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener); } protected String encrypt(String value) { try { final byte[] bytes = value != null ? value.getBytes(UTF8) : new byte[0]; SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(new PBEKeySpec(getSpecialCode())); Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(getAndroidId() .getBytes(UTF8), 20)); return new String(Base64.encode(pbeCipher.doFinal(bytes), Base64.NO_WRAP), UTF8); } catch (Exception e) { throw new RuntimeException(e); } } private String getAndroidId() { String androidId = Settings.Secure .getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); //doing this to ease unit testing in mock environments where shadowed contentresolver returns null if (androidId == null) { androidId = "01234567"; } return androidId; } protected String decrypt(String value) { try { final byte[] bytes = value != null ? Base64.decode(value, Base64.DEFAULT) : new byte[0]; SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(new PBEKeySpec(getSpecialCode())); Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(getAndroidId() .getBytes(UTF8), 20)); return new String(pbeCipher.doFinal(bytes), UTF8); } catch (Exception e) { throw new RuntimeException(e); } } }