EncryptedSharedPreferences is a component of the AndroidX Security library that provides transparent encryption of data saved through the SharedPreferences API. Unlike regular SharedPreferences, where data is stored in a plain XML file, EncryptedSharedPreferences automatically encrypts keys and values before writing to disk. According to Android Developers, the library uses AES-256 GCM for values and AES-256 SIV (RFC 5297) for keys, ensuring data confidentiality and integrity.
Key Takeaways
EncryptedSharedPreferences is a class from the androidx.security.crypto package, introduced in AndroidX Security 1.0.0 (2019). It implements the SharedPreferences interface, but all write operations (putString, putInt, putBoolean, etc.) encrypt data beforehand, and read operations decrypt data before returning it.
Standard SharedPreferences save data to an XML file in the app's directory (/data/data/package/shared_prefs/). The file is not encrypted — with root access to the device or during backup analysis, all data is readable as plain XML. Authentication tokens, API keys, and personal user data become accessible to an attacker.
EncryptedSharedPreferences solves this problem at the library level: data is encrypted before writing to disk and decrypted when read. The developer does not need to call cryptographic functions manually — the API remains identical to regular SharedPreferences.
The AndroidX Security library v1.0.0 was released in December 2019. EncryptedSharedPreferences replaced the outdated approach of manual encryption via Cipher + SharedPreferences. The current stable version is 1.1.0-alpha06 (2024), supporting API 19+. The library is part of Jetpack and requires no additional permissions.
According to Google Security Blog (2024), EncryptedSharedPreferences is the recommended way to store sensitive app settings that do not require cloud synchronization. For more complex scenarios, Room with SQLCipher encryption is recommended.
EncryptedSharedPreferences uses a two-level encryption scheme: the Master Key is stored in Android Keystore, and derived keys are used for data encryption. This combines Keystore protection with the performance of symmetric encryption.
For values, AES-256 GCM (Galois/Counter Mode) is used — an authenticated encryption mode (AEAD) ensuring data confidentiality and integrity. For keys (parameter names), AES-256 SIV (RFC 5297) is applied — deterministic encryption required for key lookup without revealing its contents.
Each file in EncryptedSharedPreferences contains encrypted key-value pairs. The file structure includes: first a header with metadata (version, key identifier), then a list of encrypted entries. The file is not valid XML and cannot be read by text editors.
The MasterKey class is responsible for creating and managing a 256-bit master key stored in Android Keystore. MasterKey.Builder allows configuration of: storage type (Keystore or software), biometric protection, and key lifetime. By default, the master key is generated in Android Keystore using the AES/GCM/NoPadding algorithm.
import androidx.security.crypto.MasterKey
import androidx.security.crypto.EncryptedSharedPreferences
fun getEncryptedPrefs() {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.AES256_GCM_SPEC)
.build()
val prefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
EncryptedSharedPreferences.create takes five parameters: context, file name, master key, key encryption scheme, and value encryption scheme. The choice of schemes affects performance and security level.
AES256_SIV — deterministic encryption: identical keys always produce identical ciphertext. This is necessary for key lookup (SharedPreferences.getX(key)). Drawback: an attacker can determine which keys are used by matching repeated ciphertexts. AES256_SIV2 — an improved version with additional randomization.
For values, AES256_GCM is used. GCM adds a 12-byte IV (initialization vector) and a 16-byte authentication tag to each value. This provides confidentiality (no one can read the value) and authentication (no one can tamper with the value undetected).
The setUserAuthenticationRequired(true) method in MasterKey.Builder requires biometric confirmation before retrieving the master key from Keystore. This adds an extra layer: even if the app is running on an unlocked device, an attacker cannot read EncryptedSharedPreferences without Face ID or Touch ID.
Important: with setUserAuthenticationRequired, the master key becomes unavailable if the user changes or removes biometrics. You must handle KeyPermanentlyInvalidatedException and create a new master key with data migration.
fun createBiometricKey(): MasterKey {
return MasterKey.Builder(context)
.setKeyScheme(MasterKey.AES256_GCM_SPEC)
.setUserAuthenticationRequired(true)
.setRequestStrongBoxBacked(true)
.build()
}
fun writeSecureToken(token: String) {
try {
prefs.edit().putString("auth_token", token).apply()
} catch (e: KeyPermanentlyInvalidatedException) {
// Biometrics changed — need to recreate the key
}
}
Let's look at a complete example of integrating EncryptedSharedPreferences in an Android app using Kotlin. The androidx.security:security-crypto library is added via Gradle.
In the build.gradle (app) file, add: implementation "androidx.security:security-crypto:1.1.0-alpha06". For Kotlin projects, kotlin-stdlib is also required. MasterKey initialization happens once, typically in Application.onCreate or through a DI container.
After creating an EncryptedSharedPreferences instance, the API is no different from regular SharedPreferences. edit() returns an Editor, all methods (putString, getString, putBoolean, getBoolean) work the same way. The only difference is internal: data is encrypted on write and decrypted on read.
class AuthRepository(context: Context) {
private val prefs = createEncryptedPrefs(context)
fun saveCredentials(login: String, password: String) {
prefs.edit()
.putString("login", login)
.putString("password", password)
.apply()
}
fun getToken(): String? {
return prefs.getString("auth_token", null)
}
fun clearAll() {
prefs.edit().clear().apply()
}
}
To migrate existing data from unencrypted SharedPreferences to EncryptedSharedPreferences, you need to: read all data from the old file, create a new EncryptedSharedPreferences, write all data, and delete the old file. Google does not provide a built-in migrator — the developer implements it manually.
The choice between SharedPreferences and EncryptedSharedPreferences depends on the type of data being stored. For UI settings (theme, language, sorting), regular SharedPreferences are sufficient. For confidential information (tokens, passwords, keys), EncryptedSharedPreferences is mandatory.
EncryptedSharedPreferences is slower than regular ones due to cryptographic operations. Writing a single string value takes ~5-15 ms (depending on data size and AES hardware acceleration). Reading takes 2-5 ms. For most apps this is unnoticeable, but for batch operations (migration, restoration) use apply() instead of commit().
Regular SharedPreferences provide no cryptographic protection: the XML file can be read by any process with root access or via adb backup. EncryptedSharedPreferences encrypts data at the application level, and the master key is stored in Android Keystore with optional hardware protection (StrongBox).
| Feature | SharedPreferences | EncryptedSharedPreferences |
|---|---|---|
| Storage | Plain XML | Encrypted binary file |
| Encryption | None | AES-256 GCM + SIV |
| Key Protection | None | Android Keystore + StrongBox |
| Performance | 0.1-1 ms | 2-15 ms |
| Recommendation | UI Settings | Tokens, Keys, PII |
Use EncryptedSharedPreferences for storing: OAuth refresh tokens, API keys for external services, user email or phone number, and sensitive app settings (PIN, authentication flags). EncryptedSharedPreferences is not suitable for storing biometric data or large documents — use EncryptedFile or Room with SQLCipher instead.
The general rule: if a data leak would harm the user or business — use EncryptedSharedPreferences. If the data is only cosmetic (theme, language, sorting) — regular SharedPreferences. It makes sense to implement EncryptedSharedPreferences from the start, without refactoring: replacing it in an existing project requires migration and handling of old unencrypted data.
Remember that EncryptedSharedPreferences does not protect data while the app is running — only on disk. If an attacker has access to the process memory, decrypted data can be intercepted. Use additional protection: ProGuard/DexGuard for obfuscation.
Frequently Asked Questions
Jetpack DataStore is a more modern alternative to SharedPreferences, based on Flow and Kotlin coroutines. DataStore does not encrypt data by default, but can be combined with EncryptedSharedPreferences or used with manual encryption via Proto DataStore with cryptographic protocols.
Not recommended. EncryptedSharedPreferences is designed for small volumes (up to 100-200 KB). For larger data, use Room with SQLCipher or file encryption via EncryptedFile from the same AndroidX Security library.
No, there is no automatic schema migration. When changing the data structure, the developer must manually read old data through the old KeyGen and write it through the new one. It is recommended to store the schema version in a separate parameter.
AndroidX Security 1.0.0 supports API 19+ (Android KitKat). Version 1.1.0-alpha06 also supports API 19+. StrongBox requires API 28+ and a device with hardware support (Google Pixel 3+, Samsung Galaxy S9+).
Yes, refresh token is one of the primary use cases. AES-256 GCM encryption, master key in Keystore, biometric protection — a sufficient level for OAuth tokens. For short-lived access tokens, it is also suitable, although some teams prefer to store them in memory.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.