/* * This file is part of FanshaweConnect. * * Copyright 2013 Gabriel Castro (c) * * FanshaweConnect is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * FanshaweConnect is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with FanshaweConnect. If not, see <http://www.gnu.org/licenses/>. */ package ca.GabrielCastro.fanshaweconnect.util; import android.annotation.TargetApi; import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import android.provider.Settings; import android.util.Base64; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; 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. */ /** * wraps a SharedPreferences object and "encrypts" all values with a standard key * and generated salt */ public class ObfuscatedSharedPreferences implements SharedPreferences { protected static final String UTF8 = "utf-8"; private static final String KEY_VERSION = "OSP_version"; // completely randomly generated private static final char[] SEKRIT = SecretKeyGenerator.getSecretKey(); private final byte[] SALT; private final String saltHash64; protected SharedPreferences delegate; private final WeakHashMap<OnSharedPreferenceChangeListener, OnSharedPreferenceChangeListener> mDelegateMap = new WeakHashMap<OnSharedPreferenceChangeListener, OnSharedPreferenceChangeListener>(); public ObfuscatedSharedPreferences(Context context, SharedPreferences delegate) { AnalyticsWrapper.Timer timer = AnalyticsWrapperImpl .getTimer(context, "ObfuscatedSharedPreferences", "create", null).start(); this.delegate = delegate; try { PRNGFixes.apply(); SALT = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID).getBytes(UTF8); saltHash64 = Base64.encodeToString(SHA1(SALT, 5), Base64.DEFAULT); } catch (Exception e) { throw new RuntimeException(e); } boolean shouldClear = false; try { shouldClear = !saltHash64.equals(this.getString(KEY_VERSION, null)); } catch(Exception e) { shouldClear = true; } finally { if (shouldClear) { this.edit() .clear() .putString(KEY_VERSION, saltHash64) .commit(); } } timer.end().submit(); } public static ObfuscatedSharedPreferences create(Context context, String name) { return new ObfuscatedSharedPreferences( context.getApplicationContext(), context.getSharedPreferences(name, Context.MODE_PRIVATE) ); } protected static byte[] SHA1(byte[] text, int iterations) throws NoSuchAlgorithmException { if (iterations > 0) { text = SHA1(text, --iterations); } MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] sha1hash; md.update(text, 0, text.length); sha1hash = md.digest(); return sha1hash; } public ObfuscatedEditor edit() { return new ObfuscatedEditor(); } @Override public Map<String, ?> getAll() { Map<String, ?> encrypted = delegate.getAll(); Map<String, Object> decrypted = new HashMap<String, Object>(encrypted.size()); for (Map.Entry<String, ?> entry : encrypted.entrySet()) { String key = entry.getKey(); String value = decrypt(entry.getValue().toString()); if (value.length() == 1 && value.charAt(0) == 0) { value = null; decrypted.put(key, value); continue; } try { Boolean isTure = "ture".equalsIgnoreCase(value); Boolean isFalse = "false".equalsIgnoreCase(value); if (isTure == isFalse) { throw new ParseException(value.toString(), 0); } decrypted.put(entry.getKey(), isTure); continue; } catch (ParseException e) { } try { Integer v = Integer.valueOf(value); decrypted.put(entry.getKey(), v); continue; } catch (NumberFormatException e) { } try { Long v = Long.valueOf(value); decrypted.put(entry.getKey(), v); continue; } catch (NumberFormatException e) { } try { Float v = Float.valueOf(value); decrypted.put(entry.getKey(), v); continue; } catch (NumberFormatException e) { } decrypted.put(key, value); } return decrypted; } @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; } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public Set<String> getStringSet(String key, Set<String> defValues) { Set<String> values = delegate.getStringSet(key, null); if (values == null) { return defValues; } for (String v : new ArrayList<String>(values)) { values.remove(v); values.add(decrypt(v)); } return values; } @Override public boolean contains(String s) { return delegate.contains(s); } @Override public void registerOnSharedPreferenceChangeListener(final OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { OnSharedPreferenceChangeListener delegateListener = new OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { onSharedPreferenceChangeListener.onSharedPreferenceChanged(ObfuscatedSharedPreferences.this, key); } }; mDelegateMap.put(onSharedPreferenceChangeListener, delegateListener); delegate.registerOnSharedPreferenceChangeListener(delegateListener); } @Override public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { delegate.unregisterOnSharedPreferenceChangeListener(mDelegateMap.get(onSharedPreferenceChangeListener)); mDelegateMap.remove(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(SEKRIT)); Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20)); byte[] doneFinal = pbeCipher.doFinal(bytes); return new String(Base64.encode(doneFinal, Base64.NO_WRAP), UTF8); } catch (Exception e) { throw new RuntimeException(e); } } 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(SEKRIT)); Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20)); byte[] doneFinal = pbeCipher.doFinal(bytes); return new String(doneFinal, UTF8); } catch (Exception e) { throw new RuntimeException(e); } } public class ObfuscatedEditor implements SharedPreferences.Editor { protected SharedPreferences.Editor delegate; private ObfuscatedEditor() { this.delegate = ObfuscatedSharedPreferences.this.delegate.edit(); } @Override public ObfuscatedEditor putBoolean(String key, boolean value) { delegate.putString(key, encrypt(Boolean.toString(value))); return this; } @Override public ObfuscatedEditor putFloat(String key, float value) { delegate.putString(key, encrypt(Float.toString(value))); return this; } @Override public ObfuscatedEditor putInt(String key, int value) { delegate.putString(key, encrypt(Integer.toString(value))); return this; } @Override public ObfuscatedEditor putLong(String key, long value) { delegate.putString(key, encrypt(Long.toString(value))); return this; } @Override public ObfuscatedEditor putString(String key, String value) { delegate.putString(key, encrypt(value)); return this; } @Override public SharedPreferences.Editor putStringSet(String key, Set<String> values) { throw new UnsupportedOperationException(); } @TargetApi(Build.VERSION_CODES.GINGERBREAD) @Override public void apply() { delegate.apply(); } @Override public ObfuscatedEditor clear() { delegate.clear(); return this; } @Override public boolean commit() { return delegate.commit(); } @Override public ObfuscatedEditor remove(String s) { delegate.remove(s); return this; } } }