How to Secure Android Shared Preferences?

前端 未结 9 2151
我在风中等你
我在风中等你 2020-11-28 22:11

The common location where SharedPreferences are stored in Android apps is:

/data/data//shared_prefs/
         


        
9条回答
  •  攒了一身酷
    2020-11-28 22:35

    Kotlin example for dual purpose encrypted & unencrypted shared preferences using anrdoidx's security-crypto library (min API 23).

    • Decent introduction to EncryptedSharedPreferences: garageprojects.tech/

    I use Dagger2 to inject this as a @Singleton where needed.

    Use the @Name annotation in your Dagger Modules to differentiate between the SharedPreferences instances and you can have 2 separate .xml files (1 encrypted, 1 unencrypted) to read/write to/from.

    • Decent introduction to Dagger2: codinginflow.com
    • Dagger Module & field injection example for the following code: stackoverflow.com

    Add to dependenies in build.gradle:

    implementation "androidx.security:security-crypto:1.0.0-beta01"


    import android.content.Context
    import android.content.SharedPreferences
    import androidx.security.crypto.EncryptedSharedPreferences
    
    class Prefs(prefsName: String, context: Context) {
    
        private lateinit var ANDX_SECURITY_KEY_KEYSET: String
        private lateinit var ANDX_SECURITY_VALUE_KEYSET: String
        private lateinit var cntext: Context
        private lateinit var prefName: String
    
        private lateinit var prefs: SharedPreferences
    
        constructor(
            prefsName: String,
            context: Context,
            masterKeyAlias: String,
            prefKeyEncryptionScheme: EncryptedSharedPreferences.PrefKeyEncryptionScheme,
            prefValueEncryptionScheme: EncryptedSharedPreferences.PrefValueEncryptionScheme
        ): this(prefsName, context) {
            ANDX_SECURITY_KEY_KEYSET = "__androidx_security_crypto_encrypted_prefs_key_keyset__"
            ANDX_SECURITY_VALUE_KEYSET =    "__androidx_security_crypto_encrypted_prefs_value_keyset__"
            cntext = context
            prefName = prefsName
            prefs =
                EncryptedSharedPreferences.create(
                    prefsName,
                    masterKeyAlias,
                    context,
                    prefKeyEncryptionScheme,
                    prefValueEncryptionScheme
                )
        }
    
        init {
            if (!::ANDX_SECURITY_KEY_KEYSET.isInitialized) {
                prefs =
                    context.getSharedPreferences(
                        prefsName,
                        Context.MODE_PRIVATE
                    )
            }
        }
    
        companion object {
            const val INVALID_BOOLEAN: Boolean = false
            const val INVALID_FLOAT: Float = -11111111111F
            const val INVALID_INT: Int = -1111111111
            const val INVALID_LONG: Long = -11111111111L
            const val INVALID_STRING: String = "INVALID_STRING"
            val INVALID_STRING_SET: Set = setOf(INVALID_STRING)
        }
    
        /**
         * OnChangeListener
         * */
        fun registerOnSharedPreferenceChangeListener(
            listener: SharedPreferences.OnSharedPreferenceChangeListener) =
            prefs.registerOnSharedPreferenceChangeListener(listener)
    
        fun unregisterOnSharedPreferenceChangeListener(
            listener: SharedPreferences.OnSharedPreferenceChangeListener) =
            prefs.unregisterOnSharedPreferenceChangeListener(listener)
    
        /**
         * Read Shared Prefs
         * */
        fun contains(key: String): Boolean =
            prefs.contains(key)
    
        fun getAll(): Map =
            prefs.all
    
        // Returns null if the Boolean value is not in
        //  Shared Preferences
        fun read(key: String): Boolean? =
            if (contains(key)) {
                read(key, INVALID_BOOLEAN)
            } else {
                null
            }
    
        // Boolean
        fun read(key: String, returnIfInvalid: Boolean): Boolean =
            prefs.getBoolean(key, returnIfInvalid)
    
        // Float
        fun read(key: String, returnIfInvalid: Float): Float =
            prefs.getFloat(key, returnIfInvalid)
    
        // Int
        fun read(key: String, returnIfInvalid: Int): Int =
            prefs.getInt(key, returnIfInvalid)
    
        // Long
        fun read(key: String, returnIfInvalid: Long): Long =
            prefs.getLong(key, returnIfInvalid)
    
        // Set
        fun read(key: String, returnIfInvalid: Set): Set? =
            prefs.getStringSet(key, returnIfInvalid)
    
        // String
        fun read(key: String, returnIfInvalid: String): String? =
            prefs.getString(key, returnIfInvalid)
    
        /**
         * Modify Shared Prefs
         * */
        fun clear() {
            if (::ANDX_SECURITY_KEY_KEYSET.isInitialized) {
                val clearTextPrefs = cntext.getSharedPreferences(prefName, Context.MODE_PRIVATE)
                val keyKeyset = clearTextPrefs.getString(ANDX_SECURITY_KEY_KEYSET, INVALID_STRING)
                val valueKeyset = clearTextPrefs.getString(ANDX_SECURITY_VALUE_KEYSET, INVALID_STRING)
                if (keyKeyset != null && keyKeyset != INVALID_STRING
                    && valueKeyset != null && valueKeyset != INVALID_STRING) {
                    if (!clearTextPrefs.edit().clear().commit()) {
                        clearTextPrefs.edit().clear().apply()
                    }
                    if (!clearTextPrefs.edit().putString(ANDX_SECURITY_KEY_KEYSET, keyKeyset).commit()) {
                        clearTextPrefs.edit().putString(ANDX_SECURITY_KEY_KEYSET, keyKeyset).apply()
                    }
                    if (!clearTextPrefs.edit().putString(ANDX_SECURITY_VALUE_KEYSET, valueKeyset).commit()) {
                        clearTextPrefs.edit().putString(ANDX_SECURITY_VALUE_KEYSET, valueKeyset).apply()
                    }
                }
            } else {
                if (!prefs.edit().clear().commit()) {
                    prefs.edit().clear().apply()
                }
            }
        }
    
        fun remove(key: String) {
            if (!prefs.edit().remove(key).commit()) {
                prefs.edit().remove(key).apply()
            }
        }
    
        // Boolean
        fun write(key: String, value: Boolean) {
            if (!prefs.edit().putBoolean(key, value).commit()) {
                prefs.edit().putBoolean(key, value).apply()
            }
        }
    
        // Float
        fun write(key: String, value: Float) {
            if (!prefs.edit().putFloat(key, value).commit()) {
                prefs.edit().putFloat(key, value).apply()
            }
        }
    
        // Int
        fun write(key: String, value: Int) {
            if (!prefs.edit().putInt(key, value).commit()) {
                prefs.edit().putInt(key, value).apply()
            }
        }
    
        // Long
        fun write(key: String, value: Long) {
            if (!prefs.edit().putLong(key, value).commit()) {
                prefs.edit().putLong(key, value).apply()
            }
        }
    
        // Set
        fun write(key: String, value: Set) {
            if (!prefs.edit().putStringSet(key, value).commit()) {
                prefs.edit().putStringSet(key, value).apply()
            }
        }
    
        // String
        fun write(key: String, value: String) {
            if (!prefs.edit().putString(key, value).commit()) {
                prefs.edit().putString(key, value).apply()
            }
        }
    }
    

    An alternative option to using Dagger2 to inject as a @Singleton could be to:

    AppPrefs.kt

    object AppPrefs {
        lateinit var encryptedPrefs: Prefs
        lateinit var prefs: Prefs
    
        // Add your key strings here...
    
        fun initEncryptedPrefs(context: Context) {
            encryptedPrefs =
                Prefs(
                    "ENCRYPTED_PREFS",
                    context,
                    MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),
                    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
                )
        }
    
        fun initPrefs(context: Context) {
            prefs = Prefs("PREFS", context)
        }
    }
    

    Application.kt

    class Application: Application() {
        override fun onCreate() {
            super.onCreate()
            AppPrefs.initEncryptedPrefs(this.applicationContext)
            AppPrefs.initPrefs(this.applicationContext)
        }
    }
    

    Then just call from where ever AppPrefs.prefs or AppPrefs.encryptedPrefs

提交回复
热议问题