TEK292
TEK292

Reputation: 261

AutoBackUp with EncryptedSharedPreferences failing to restore

I am using EncryptedSharedPreferences to store user information locally (see this if you are not familiar). I have implemented AutoBackUp with BackUp rules. I backed up the preferences, cleared data on my app, and attempted to restore the data (following the steps outlined for backup and restore).

Looking at Device File Explorer in Android Studio, I can confirm that my Preferences file is being restored (it is properly named and there is encrypted data in it). However, my app functions as if the preferences file does not exist.

What am I missing?

Preferences code:

class PreferenceManager(context: Context) {
    companion object {
        private const val KEY_STORE_ALIAS = "APP_KEY_STORE"
        private const val privatePreferences = "APP_PREFERENCES"
    }

    // See https://developer.android.com/topic/security/data#kotlin for more info
    private val sharedPreferences = EncryptedSharedPreferences.create(
        privatePreferences,
        KEY_STORE_ALIAS,
        context,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

    init {
        //val all = sharedPreferences.all
        //for (item in all) {
            //Log.e("PREFERENCES", "${item.key} - ${item.value}")
        //}
    }

    @SuppressLint("ApplySharedPref")
    fun clear() {
        // Normally you want apply, but we need the changes to be done immediately
        sharedPreferences.edit().clear().commit()
    }

    fun readBoolean(key: String, defaultValue: Boolean): Boolean {
        return sharedPreferences.getBoolean(key, defaultValue)
    }

    fun readDouble(key: String): Double {
        return sharedPreferences.getFloat(key, 0f).toDouble()
    }

    fun readString(key: String): String {
        return sharedPreferences.getString(key, "")!!
    }

    fun removePreference(key: String) {
        sharedPreferences.edit().remove(key).apply()
    }

    fun writeBoolean(key: String, value: Boolean) {
        sharedPreferences.edit().putBoolean(key, value).apply()
    }

    fun writeDouble(key: String, value: Double) {
        sharedPreferences.edit().putFloat(key, value.toFloat()).apply()
    }

    fun writeString(key: String, value: String) {
        sharedPreferences.edit().putString(key, value).apply()
    }
}

I am not implementing a BackupAgent currently.

Upvotes: 3

Views: 3121

Answers (2)

Artur Latoszewski
Artur Latoszewski

Reputation: 793

As @Floj12 mentioned EncryptedSharedPreferences use Keystore and you cannot back up the Keystore, so when your encrypted data will be restored you won't be able to decrypt it. This is very sad that Google force two things that don't work together. Keystore on Android doesn't have a backup option like Keychain on iOS.

Here I will give you more option how can you backup the data:

  • Store user data one backend
  • Use a user-stored token to decrypt the backup
  • Have a static password for all apps
  • Export backup manually by the user in settings

I wrote about it more here: https://medium.com/@thecodeside/android-auto-backup-keystore-encryption-broken-heart-love-story-8277c8b10505

Upvotes: 4

FloJ12
FloJ12

Reputation: 122

From my understanding Jetpack Security relies on keys that are generated on the device hardware, and so it is that you cannot rely on that the original key is still there after a backup restore (think about changed devices).

Encryption is only as secure as the security of the key, and as long as it cannot leave the Keystore or the device, backup and restore cannot work automatically (without user interaction).

My approach (1) would be that you ask the user for a password, encrypt your regular shared preferences based on that password (maybe with another encryption library: for example https://github.com/iamMehedi/Secured-Preference-Store), and save the password with encryptedsharedpreferences from Jetpack. After restore of the backup ask the user for the password, save it again with Jetpack and decrypt the regular SharedPreferences. That way even when the hardware keystore changes, you can restore the backup. The disadvantage is, that the user needs to remember a password.

I follow this approach with my app, just not with sharedpreferences (they are not sensible in my use case), but with the app database.

Another approach (2) would be to check for encrypted backups (available from Pie on), if you are only concerned about backups in the cloud. With that approach you don't encrypt the sharedpreferences locally, but the backups are encrypted by default. If you need local encryption, this approach is not for you, but the advantage is, that the user must only type in his/her lockscreen password on restore of the backups and after that everything gets restored without further user interaction. A combination is also thinkable and is preferrable, if you can live without local encryption: Approach 1 for pre-9 and Approach 2 for post-9.

Upvotes: 5

Related Questions