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);
}
}
}