【问题标题】:Is there a way to integrate EncryptedSharedPreference with PreferenceScreen?有没有办法将 EncryptedSharedPreference 与 PreferenceScreen 集成?
【发布时间】:2019-10-23 04:24:49
【问题描述】:

我是安卓开发新手。 目前,我想加密一个名为 Shared Preference 的自定义并与 PreferenceScreen 集成但未能这样做。我正在使用依赖项:

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

我曾试图研究这两个功能集成的相关信息,但没有找到相关信息。

根据我的测试,我有一个现有的加密共享偏好并测试了以下 API:

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

但它最终以普通值保存了偏好。

我的问题:

  1. 现阶段是否可以将这两个功能集成在一起?
  2. PreferenceScreen 是否提供我不知道的加密功能?
  3. 如果我坚持使用 EncryptedSharedPreference,我创建一个看起来像偏好屏幕的自定义活动会更好吗?

【问题讨论】:

    标签: java sharedpreferences preferencescreen android-jetpack-security


    【解决方案1】:

    A1:是的,有可能。

    A3:您可以通过以下方式利用系统提供的设置。

    由于 Kotlin 在一段时间内是首选的一等公民,现在我将在 Kotlin 中展示它,@Rikka 在另一个答案中有一个 Java 版本。对于 Kotlin 来说,诀窍是仍然设置 preferencesDataSource,它是这样的:

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

    EncryptedPreferenceDataStore 的 Kotlin 版本:我使用 also 关键字作为单例,类似于 Singleton with parameter in Kotlin 中与 Google 源代码相关的 Room 示例

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

    也许通过双重同步检查可以使线程更加安全?

    【讨论】:

      【解决方案2】:

      使用getPreferenceManager().setPreferenceDataStore(PreferenceDataStore)PreferenceDataStore 提供了更改首选项的加载/保存方式的能力。

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

      【讨论】:

      • 像魅力一样工作!!
      【解决方案3】:

      在将 EncryptedSharePreferences 与 AndroidX Preferences GUI 集成时遇到了一些问题。

      • PreferenceManager 无法全局设置默认首选项数据存储,因为它只能检索默认共享首选项(未加密变体并与应用程序包名称相关联),而不能将默认共享首选项设置为加密变体. PreferenceManager.getDefaultSharedPreferences(context); 没有对应的 set 方法。
      • PreferenceManager 只能由具有相同库组的包创建。

      我创建的解决方案是不依赖 SharedPreferences,而是利用 PreferenceFragmentCompat 写入 EncryptedPreferenceDataStore。但是,这仍然会带来默认值在用户进入 Preference 屏幕之前不会初始化的问题。

      依赖关系

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

      用法

          EncryptedPreferenceDataStore prefs = EncryptedPreferenceDataStore.getInstance(getContext());
          boolean bIsXXX = prefs.getBoolean(getString(R.string.pref_access_xxx), true);
      

      【讨论】:

      • 既然mSharedPreferences不是Fragment的成员,那么如何un-/registerOnSharedPreferenceChangeListener呢?
      猜你喜欢
      • 2020-07-16
      • 2011-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-07-06
      • 2014-03-14
      相关资源
      最近更新 更多