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.
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.