Time for encryption? EncryptedSharedPreferences and Migration — Android|Kotlin
If you think it is time to move from conventional SharedPreferences to EncryptedSharedPreferences then this is the right place for you to read and implement.
You can follow up on the Android developer website.
SharedPreferences
Shared Preferences is the way in which one can store and retrieve small amounts of primitive data as key/value pairs to a file on the device storage such as String, int, float, Boolean that make up your preferences in an XML file inside the app on the device storage.
It stores data in plain text. Which means you should not save sensitive data in plain text.
Implementation
private val sharedPreference = context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)fun getValueString(KEY_NAME: String, defValue: String? = null): String? =
sharedPreference.getString(KEY_NAME, defValue)private fun doSave(key: String, value: Any) {
with(sharedPreference.edit()) {
when (value) {
is Int -> putInt(key, value)
is Long -> putLong(key, value)
is Float -> putFloat(key, value)
is String -> putString(key, value)
is Boolean -> putBoolean(key, value)
}
commit()
}
}
You can verify from ‘Device File Explorer’ that it is saved in plain text. If you want then you can navigate to data\data\<your app id>\shared_prefs
EncryptedSharedPreferences
EncryptedSharedPreferences is a wrapper class over SharedPreferences which encrypts data and then saves it in a secure way.
Implementation
Start with simply adding this into your app/build.gradle
implementation "androidx.security:security-crypto:1.1.0-alpha03"
Then create masterKey to encrypt or decrypt data. AES256_GCM is the only option here at the moment.
private val masterKey =
MasterKey.Builder(context.applicationContext, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
EncryptedSharedPreferences works with minSDK ≥ 23, then add a check if minSDK version is less than 23
private val sharedPreference =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
EncryptedSharedPreferences.create(
context.applicationContext,
"encrypted_$prefsName",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
} else {
context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
}
Read/write is the same as SharedPreferences read/write.
You can see now that your data is encrypted:
Migration
You would probably want to migrate your unencrypted data to an encrypted one. If you have apps already being used and would like to simply publish these changes then do this migration which works perfectly fine.
private val masterKey =
MasterKey.Builder(context.applicationContext, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
private val sharedPreference =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
EncryptedSharedPreferences.create(
context.applicationContext,
"encrypted_$prefsName",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
} else {
context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
}
private val oldSharePref = context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
Create two objects
1. SharedPreferences
2. EncryptedSharedPreferences
And then check if the old one has data then move to the encrypted file. Remember to name a new preference file with some other name. Finally, check with the constructor.
init {
// Check if the new ESP has been initialised
if (oldSharePref.all.isNotEmpty()) {
// Copy each item into the ESP
oldSharePref.copyTo(sharedPreference)
oldSharePref.clear()
}
}private fun SharedPreferences.copyTo(dest: SharedPreferences) {
for (entry in all.entries) {
val key = entry.key
val value: Any? = entry.value
dest.set(key, value)
}
}
@Suppress("UNCHECKED_CAST")
private fun SharedPreferences.set(key: String, value: Any?) {
when (value) {
is String? -> edit { it.putString(key, value) }
is Int -> edit { it.putInt(key, value.toInt()) }
is Boolean -> edit { it.putBoolean(key, value) }
is Float -> edit { it.putFloat(key, value.toFloat()) }
is Long -> edit { it.putLong(key, value.toLong()) }
is Set<*> -> edit { it.putStringSet(key, value as Set<String>) }
else -> {
Log.e("Error", "SharedPreferences Unsupported Type: $value")
}
}
}
Now your old sharedPreference file should look like this:
I hope you enjoyed this article. If you want to check the full code then check on the below github link, you can find many useful tips and extension functions.
Don’t forget to clap 👏