Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to integrate EncryptedSharedPreference with PreferenceScreen?

I am new to android development. Currently, I would like to encrypt a custom named Shared Preference and integrate with PreferenceScreen but failed to do so. I am using dependencies:

  1. androidx.security:security-crypto:1.0.0-alpha02 [EncryptedSharedPreference]
  2. androidx.preference:preference:1.1.0 [PreferenceScreen]

I had tried to research the related information about integration of these 2 features but no related information found.

From my testing, I had an existing encrypted shared preference and tested the following API:

getPreferenceManager().setSharedPreferencesName("MyShared"); //MyShared Is custom named preference.

But it ended up save the preference in plain value.

My Questions:

  1. Is it possible to integrate these 2 features together in current stage?
  2. Does PreferenceScreen provide encrypted feature as I am not aware of?
  3. If I am insist to use EncryptedSharedPreference, will it be better that I create a custom activity look like preference screen?
like image 893
Skeiths Chew Avatar asked Oct 23 '19 04:10

Skeiths Chew


Video Answer


3 Answers

A1: Yes it's possible.

A3: You can take advantage of system provided settings the following way.

Since Kotlin is the preferred first class citizen for a while now I'll show it in Kotlin, @Rikka has a Java version in another answer. For Kotlin the trick is to still set the preferencesDataSource, it goes this way:

class SettingsFragment : PreferenceFragmentCompat() {
    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        preferenceManager.preferenceDataStore =
            EncryptedPreferenceDataStore.getInstance(requireContext())

        // Load the preferences from an XML resource
        setPreferencesFromResource(R.xml.preferences, rootKey)
    }

The Kotlin version of the EncryptedPreferenceDataStore: I'm using the also keyword for the singleton, similarly as the Google source code related Room example in Singleton with parameter in Kotlin

class EncryptedPreferenceDataStore private constructor(context: Context) : PreferenceDataStore() {
    companion object {
        private const val SHARED_PREFERENCES_NAME = "secret_shared_preferences"

        @Volatile private var INSTANCE: EncryptedPreferenceDataStore? = null

        fun getInstance(context: Context): EncryptedPreferenceDataStore =
            INSTANCE ?: synchronized(this) {
                INSTANCE ?: EncryptedPreferenceDataStore(context).also { INSTANCE = it }
            }
    }

    private var mSharedPreferences: SharedPreferences
    private lateinit var mContext: Context

    init {
        try {
            mContext = context
            val masterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
                .build()

            mSharedPreferences = EncryptedSharedPreferences.create(
                context,
                SHARED_PREFERENCES_NAME,
                masterKey,
                EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
            )
        } catch (e: Exception) {
            // Fallback, default mode is Context.MODE_PRIVATE!
            mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
        }
    }

    override fun putString(key: String, value: String?) {
        mSharedPreferences.edit().putString(key, value).apply()
    }

    override fun putStringSet(key: String, values: Set<String>?) {
        mSharedPreferences.edit().putStringSet(key, values).apply()
    }

    override fun putInt(key: String, value: Int) {
        mSharedPreferences.edit().putInt(key, value).apply()
    }

    override fun putLong(key: String, value: Long) {
        mSharedPreferences.edit().putLong(key, value).apply()
    }

    override fun putFloat(key: String, value: Float) {
        mSharedPreferences.edit().putFloat(key, value).apply()
    }

    override fun putBoolean(key: String, value: Boolean) {
        mSharedPreferences.edit().putBoolean(key, value).apply()
    }

    override fun getString(key: String, defValue: String?): String? {
        return mSharedPreferences.getString(key, defValue)
    }

    override fun getStringSet(key: String, defValues: Set<String>?): Set<String>? {
        return mSharedPreferences.getStringSet(key, defValues)
    }

    override fun getInt(key: String, defValue: Int): Int {
        return mSharedPreferences.getInt(key, defValue)
    }

    override fun getLong(key: String, defValue: Long): Long {
        return mSharedPreferences.getLong(key, defValue)
    }

    override fun getFloat(key: String, defValue: Float): Float {
        return mSharedPreferences.getFloat(key, defValue)
    }

    override fun getBoolean(key: String, defValue: Boolean): Boolean {
        return mSharedPreferences.getBoolean(key, defValue)
    }
}

Maybe it could be even more thread safe by double synchronized checking?

like image 50
Csaba Toth Avatar answered Oct 17 '22 01:10

Csaba Toth


Use getPreferenceManager().setPreferenceDataStore(PreferenceDataStore). PreferenceDataStore provides the ability to change how preference is loaded/saved.

A simple implementation of PreferenceDataStore:

public class SharedPreferenceDataStore extends PreferenceDataStore {

    private final SharedPreferences mSharedPreferences;

    public SharedPreferenceDataStore(@NonNull SharedPreferences sharedPreferences) {
        mSharedPreferences = sharedPreferences;
    }

    @NonNull
    public SharedPreferences getSharedPreferences() {
        return mSharedPreferences;
    }

    @Override
    public void putString(String key, @Nullable String value) {
        mSharedPreferences.edit().putString(key, value).apply();
    }

    @Override
    public void putStringSet(String key, @Nullable Set<String> values) {
        mSharedPreferences.edit().putStringSet(key, values).apply();
    }

    @Override
    public void putInt(String key, int value) {
        mSharedPreferences.edit().putInt(key, value).apply();
    }

    @Override
    public void putLong(String key, long value) {
        mSharedPreferences.edit().putLong(key, value).apply();
    }

    @Override
    public void putFloat(String key, float value) {
        mSharedPreferences.edit().putFloat(key, value).apply();
    }

    @Override
    public void putBoolean(String key, boolean value) {
        mSharedPreferences.edit().putBoolean(key, value).apply();
    }

    @Nullable
    @Override
    public String getString(String key, @Nullable String defValue) {
        return mSharedPreferences.getString(key, defValue);
    }

    @Nullable
    @Override
    public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
        return mSharedPreferences.getStringSet(key, defValues);
    }

    @Override
    public int getInt(String key, int defValue) {
        return mSharedPreferences.getInt(key, defValue);
    }

    @Override
    public long getLong(String key, long defValue) {
        return mSharedPreferences.getLong(key, defValue);
    }

    @Override
    public float getFloat(String key, float defValue) {
        return mSharedPreferences.getFloat(key, defValue);
    }

    @Override
    public boolean getBoolean(String key, boolean defValue) {
        return mSharedPreferences.getBoolean(key, defValue);
    }
}
getPreferenceManager().setPreferenceDataStore(new SharedPreferenceDataStore(EncryptedSharedPreferences.create()));
like image 26
Rikka Avatar answered Oct 16 '22 23:10

Rikka


There are a few issues that I encounter with integrating EncryptedSharePreferences with AndroidX Preferences GUI.

  • PreferenceManager is not able to set the default preference data store globally, because it is only possible to retrieve the default shared preferences (un-encrypted variant and tied to application package name) and not set the default shared preferences to the encrypted variant. PreferenceManager.getDefaultSharedPreferences(context); does not have a corresponding set method.
  • PreferenceManager can only be created by packages with the same library group.

The solution I have created is to not depend on SharedPreferences, but to utilise the PreferenceFragmentCompat to write to EncryptedPreferenceDataStore. However, this still comes with the issue that the default values are not initialised until the user enters the Preference screen.

Dependencies

dependencies {
    implementation 'androidx.preference:preference:1.1.1'
    implementation 'androidx.security:security-crypto:1.0.0-rc01'
}

PreferencesFragment

import android.app.Activity;
import android.app.ActivityManager;
import android.os.Bundle;

import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;

public class PreferencesFragment extends PreferenceFragmentCompat {

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        PreferenceManager preferenceManager = getPreferenceManager();
        preferenceManager.setPreferenceDataStore(EncryptedPreferenceDataStore.getInstance(getContext()));

        // Load the preferences from an XML resource
        setPreferencesFromResource(R.xml.preferences, rootKey);
    }

EncryptedPreferenceDataStore

import android.content.Context;
import android.content.SharedPreferences;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceDataStore;
import androidx.security.crypto.EncryptedSharedPreferences;

import java.util.Set;

public class EncryptedPreferenceDataStore extends PreferenceDataStore {

    private static final String CONFIG_FILE_NAME = "FileName";
    private static final String CONFIG_MASTER_KEY_ALIAS = "KeyAlias";
    private static EncryptedPreferenceDataStore mInstance;
    private SharedPreferences mSharedPreferences;
    private Context mContext;

    private EncryptedPreferenceDataStore(Context context) {
        try {
            mContext = context;
            mSharedPreferences = EncryptedSharedPreferences.create(
                    CONFIG_FILE_NAME,
                    CONFIG_MASTER_KEY_ALIAS,
                    context,
                    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, //for encrypting Keys
                    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ////for encrypting Values
            );
        } catch (Exception e) {
            // Fallback
            mSharedPreferences = context.getSharedPreferences(CONFIG_FILE_NAME, Context.MODE_PRIVATE);
        }
    }

    @Override
    public void putString(String key, @Nullable String value) {
        mSharedPreferences.edit().putString(key, value).apply();
    }

    @Override
    public void putStringSet(String key, @Nullable Set<String> values) {
        mSharedPreferences.edit().putStringSet(key, values).apply();
    }

    @Override
    public void putInt(String key, int value) {
        mSharedPreferences.edit().putInt(key, value).apply();
    }

    @Override
    public void putLong(String key, long value) {
        mSharedPreferences.edit().putLong(key, value).apply();
    }

    @Override
    public void putFloat(String key, float value) {
        mSharedPreferences.edit().putFloat(key, value).apply();
    }

    @Override
    public void putBoolean(String key, boolean value) {
        mSharedPreferences.edit().putBoolean(key, value).apply();
    }

    @Nullable
    @Override
    public String getString(String key, @Nullable String defValue) {
        return mSharedPreferences.getString(key, defValue);
    }

    @Nullable
    @Override
    public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
        return mSharedPreferences.getStringSet(key, defValues);
    }

    @Override
    public int getInt(String key, int defValue) {
        return mSharedPreferences.getInt(key, defValue);
    }

    @Override
    public long getLong(String key, long defValue) {
        return mSharedPreferences.getLong(key, defValue);
    }

    @Override
    public float getFloat(String key, float defValue) {
        return mSharedPreferences.getFloat(key, defValue);
    }

    @Override
    public boolean getBoolean(String key, boolean defValue) {
        return mSharedPreferences.getBoolean(key, defValue);
    }
}

Usage

    EncryptedPreferenceDataStore prefs = EncryptedPreferenceDataStore.getInstance(getContext());
    boolean bIsXXX = prefs.getBoolean(getString(R.string.pref_access_xxx), true);
like image 39
JaLooNz Avatar answered Oct 16 '22 23:10

JaLooNz