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:
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:
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?
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()));
There are a few issues that I encounter with integrating EncryptedSharePreferences with AndroidX Preferences GUI.
PreferenceManager.getDefaultSharedPreferences(context);
does not have a corresponding set method.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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With