Android Security Essentials: Keystore, EncryptedStorage, and Network Pinning
Keystore: The Foundation of Android Security
Android Keystore is a system that allows developers to store cryptographic keys in a container to make it more difficult to extract from the device. Introduced in API level 18, Keystore provides hardware-backed security if the device supports it, ensuring that the cryptographic keys are stored in a secure environment.
Key Generation with Keystore
To generate a key using Keystore, you can use the KeyGenerator class. Here’s an example of generating an AES key:
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
keyGenerator.init(
KeyGenParameterSpec.Builder("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
)
val secretKey = keyGenerator.generateKey()
In this example, “AndroidKeyStore” is the name of the keystore provider. The KeyGenParameterSpec.Builder is used to specify the purpose of the key, the block mode, and the padding. The alias “MyKeyAlias” is used to retrieve the key later.
Using the Generated Key
Once the key is generated, you can use it for encryption and decryption. Here’s how you can encrypt data using the generated key:
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val iv = cipher.iv
val encryptedBytes = cipher.doFinal("SensitiveData".toByteArray())
And to decrypt the data:
val cipherDecrypt = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, iv)
cipherDecrypt.init(Cipher.DECRYPT_MODE, secretKey, spec)
val decryptedBytes = cipherDecrypt.doFinal(encryptedBytes)
val decryptedData = String(decryptedBytes)
EncryptedSharedPreferences: Secure Data Storage
EncryptedSharedPreferences is a part of the AndroidX Security library, which provides secure storage solutions for sensitive data. Unlike regular SharedPreferences, EncryptedSharedPreferences encrypts the data before storing it, ensuring that the data is protected even if the device is compromised.
Setting Up EncryptedSharedPreferences
First, you need to add the AndroidX Security library to your project. In your build.gradle file, add the following dependency:
dependencies {
implementation "androidx.security:security-crypto:1.1.0-alpha03"
}
Using EncryptedSharedPreferences
Here’s how you can use EncryptedSharedPreferences to store and retrieve data securely:
val masterKeyAlias = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPreferences = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKeyAlias,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// Storing data
sharedPreferences.edit().putString("key", "value").apply()
// Retrieving data
val value = sharedPreferences.getString("key", null)
In this example, MasterKey is used to generate a master key that is used for encryption and decryption. The EncryptedSharedPreferences.create method takes in the context, the name of the preferences file, the master key, and the encryption schemes for keys and values.
Advantages of EncryptedSharedPreferences
– Data Encryption: All data is encrypted before being written to disk.
– Integrity: The library ensures that the data has not been tampered with.
– Ease of Use: It provides a similar API to regular SharedPreferences, making it easy to integrate into existing projects.
Network Pinning: Securing Data in Transit
Network pinning is a security mechanism that ensures your app only communicates with trusted servers by validating the server’s SSL certificate. This prevents man-in-the-middle (MITM) attacks by ensuring that the app is communicating with the intended server and not a malicious one.
Implementing Network Pinning with OkHttp
OkHttp is a popular HTTP client for Android that supports network pinning. To implement network pinning, you need to add the CertificatePinner to your OkHttpClient.
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
In this example, sha256/... and sha256=... are the SHA-256 hashes of the server’s SSL certificate. You can obtain these hashes by running the following command:
openssl x509 -in certificate.crt -noout -pubkey | openssl rsa -pubin -outform DER | openssl dgst -sha256 -binary | openssl base64
Handling Pin Validation
When the CertificatePinner is set, OkHttp will automatically validate the server’s certificate against the pinned hashes. If the validation fails, the connection will be aborted, and an exception will be thrown.
Advantages of Network Pinning
– Enhanced Security: Protects against MITM attacks by ensuring the server’s identity.
– Trust Management: Allows you to specify exactly which certificates or public keys are trusted.
Considerations for Network Pinning
– Certificate Rotation: When the server’s certificate is rotated, the pinned hashes must be updated in the app.
– App Updates: Users must update the app to receive the new hashes, which can be a limitation.
Combining Keystore, EncryptedSharedPreferences, and Network Pinning
To achieve a robust security posture, it is essential to combine these security mechanisms. Here’s how you can integrate them into your Android application:
1. Key Management with Keystore: Use Keystore to generate and manage cryptographic keys for encrypting sensitive data.
2. Secure Data Storage with EncryptedSharedPreferences: Store sensitive data in EncryptedSharedPreferences to ensure it is encrypted at rest.
3. Secure Data in Transit with Network Pinning: Use network pinning to ensure that all network communications are with trusted servers.
Example: Integrating Keystore and EncryptedSharedPreferences
// Generate a key using Keystore
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
keyGenerator.init(
KeyGenParameterSpec.Builder("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
)
val secretKey = keyGenerator.generateKey()
// Initialize EncryptedSharedPreferences
val masterKeyAlias = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPreferences = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKeyAlias,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,\n EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// Store encrypted data
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val iv = cipher.iv
val encryptedBytes = cipher.doFinal("SensitiveData".toByteArray())
val encryptedData = Base64.encodeToString(encryptedBytes, Base64.DEFAULT)
sharedPreferences.edit().putString("encrypted_key", encryptedData).apply()
// Retrieve and decrypt data
val encryptedDataRetrieved = sharedPreferences.getString("encrypted_key", null)
val encryptedBytesRetrieved = Base64.decode(encryptedDataRetrieved, Base64.DEFAULT)
val cipherDecrypt = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, iv)
cipherDecrypt.init(Cipher.DECRYPT_MODE, secretKey, spec)
val decryptedBytes = cipherDecrypt.doFinal(encryptedBytesRetrieved)
val decryptedData = String(decryptedBytes)
In this example, we first generate a key using Keystore and then use it to encrypt data before storing it in EncryptedSharedPreferences. This ensures that the data is both encrypted at rest and the keys are managed securely.
Example: Integrating Network Pinning with OkHttp
// Initialize CertificatePinner
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
.build()
// Build OkHttpClient with CertificatePinner
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
// Use the client for network requests
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// Handle failure
}
override fun onResponse(call: Call, response: Response) {
// Handle response
}
})
In this example, the CertificatePinner is used to specify the pinned hashes for the server’s SSL certificate. The OkHttpClient is then built with the CertificatePinner, ensuring that all network requests are secured by network pinning.
Conclusion
By leveraging Keystore for key management, EncryptedSharedPreferences for secure data storage, and network pinning for secure data in transit, you can significantly enhance the security posture of your Android application. These mechanisms provide a layered approach to security, ensuring that your app’s data is protected both at rest and in transit, and that it communicates only with trusted servers.





