package devliving.online.securedpreferencestore; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.UnrecoverableEntryException; import java.security.cert.CertificateException; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.crypto.NoSuchPaddingException; /** * Created by Mehedi on 8/21/16. */ public class SecuredPreferenceStore implements SharedPreferences { private final String PREF_FILE_NAME = "SPS_file"; private SharedPreferences mPrefs; private EncryptionManager mEncryptionManager; private static RecoveryHandler mRecoveryHandler; private static SecuredPreferenceStore mInstance; private SecuredPreferenceStore(Context appContext) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableEntryException, InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException, NoSuchProviderException { Log.d("SECURE-PREFERENCE", "Creating store instance"); mPrefs = appContext.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE); mEncryptionManager = new EncryptionManager(appContext, mPrefs, new KeyStoreRecoveryNotifier() { @Override public boolean onRecoveryRequired(Exception e, KeyStore keyStore, List<String> keyAliases) { if(mRecoveryHandler != null) return mRecoveryHandler.recover(e, keyStore, keyAliases, mPrefs); else throw new RuntimeException(e); } }); } public static void setRecoveryHandler(RecoveryHandler recoveryHandler) { SecuredPreferenceStore.mRecoveryHandler = recoveryHandler; } synchronized public static SecuredPreferenceStore getSharedInstance(Context appContext) throws IOException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, UnrecoverableEntryException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchProviderException, KeyStoreException { if (mInstance == null) { mInstance = new SecuredPreferenceStore(appContext); } return mInstance; } public EncryptionManager getEncryptionManager() { return mEncryptionManager; } @Override public Map<String, String> getAll() { Map<String, ?> all = mPrefs.getAll(); Map<String, String> dAll = new HashMap<>(all.size()); if (all.size() > 0) { for (String key : all.keySet()) { try { dAll.put(key, mEncryptionManager.decrypt((String) all.get(key))); } catch (Exception e) { e.printStackTrace(); } } } return dAll; } @Override public String getString(String key, String defValue) { try { String hashedKey = EncryptionManager.getHashed(key); String value = mPrefs.getString(hashedKey, null); if (value != null) return mEncryptionManager.decrypt(value); } catch (Exception e) { e.printStackTrace(); } return defValue; } @Override public Set<String> getStringSet(String key, Set<String> defValues) { try { String hashedKey = EncryptionManager.getHashed(key); Set<String> eSet = mPrefs.getStringSet(hashedKey, null); if (eSet != null) { Set<String> dSet = new HashSet<>(eSet.size()); for (String val : eSet) { dSet.add(mEncryptionManager.decrypt(val)); } return dSet; } } catch (Exception e) { e.printStackTrace(); } return defValues; } @Override public int getInt(String key, int defValue) { String value = getString(key, null); if (value != null) { return Integer.parseInt(value); } return defValue; } @Override public long getLong(String key, long defValue) { String value = getString(key, null); if (value != null) { return Long.parseLong(value); } return defValue; } @Override public float getFloat(String key, float defValue) { String value = getString(key, null); if (value != null) { return Float.parseFloat(value); } return defValue; } @Override public boolean getBoolean(String key, boolean defValue) { String value = getString(key, null); if (value != null) { return Boolean.parseBoolean(value); } return defValue; } public byte[] getBytes(String key) { String val = getString(key, null); if (val != null) { return EncryptionManager.base64Decode(val); } return null; } @Override public boolean contains(String key) { try { String hashedKey = EncryptionManager.getHashed(key); return mPrefs.contains(hashedKey); } catch (Exception e) { e.printStackTrace(); } return false; } @Override public Editor edit() { return new Editor(); } @Override public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { if (mPrefs != null) mPrefs.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener); } @Override public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener onSharedPreferenceChangeListener) { if (mPrefs != null) mPrefs.unregisterOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener); } public class Editor implements SharedPreferences.Editor { SharedPreferences.Editor mEditor; public Editor() { mEditor = mPrefs.edit(); } @Override public SharedPreferences.Editor putString(String key, String value) { try { String hashedKey = EncryptionManager.getHashed(key); String evalue = mEncryptionManager.encrypt(value); mEditor.putString(hashedKey, evalue); } catch (Exception e) { e.printStackTrace(); } return this; } @Override public SharedPreferences.Editor putStringSet(String key, Set<String> values) { try { String hashedKey = EncryptionManager.getHashed(key); Set<String> eSet = new HashSet<String>(values.size()); for (String val : values) { eSet.add(mEncryptionManager.encrypt(val)); } mEditor.putStringSet(hashedKey, eSet); } catch (Exception e) { e.printStackTrace(); } return this; } @Override public SharedPreferences.Editor putInt(String key, int value) { String val = Integer.toString(value); return putString(key, val); } @Override public SharedPreferences.Editor putLong(String key, long value) { String val = Long.toString(value); return putString(key, val); } @Override public SharedPreferences.Editor putFloat(String key, float value) { String val = Float.toString(value); return putString(key, val); } @Override public SharedPreferences.Editor putBoolean(String key, boolean value) { String val = Boolean.toString(value); return putString(key, val); } public SharedPreferences.Editor putBytes(String key, byte[] bytes) { if (bytes != null) { String val = EncryptionManager.base64Encode(bytes); return putString(key, val); } else return remove(key); } @Override public SharedPreferences.Editor remove(String key) { try { String hashedKey = EncryptionManager.getHashed(key); mEditor.remove(hashedKey); } catch (Exception e) { e.printStackTrace(); } return this; } @Override public SharedPreferences.Editor clear() { mEditor.clear(); return this; } @Override public boolean commit() { return mEditor.commit(); } @Override public void apply() { mEditor.apply(); } } public interface KeyStoreRecoveryNotifier{ /** * * @param e * @param keyStore * @param keyAliases * @return true if the error could be resolved */ boolean onRecoveryRequired(Exception e, KeyStore keyStore, List<String> keyAliases); } }