Contact
Stories

Development

5 min read

How to Encrypt Data on Android With Jetpack Security

Damir
Damir
Android Development

We are all aware that it’s important to store data securely inside an app. With the Android Jetpack Security library, this is easier than ever. In just a few lines of code, you can set up the data encryption and decryption system. Additionally, we are going to cover the usage of BiometricPrompt as an extra level of security, using time-bound keys.

Unlike iOS, where it’s possible to store your password, PIN, certificates, etc. directly in KeyChain, Android uses a different approach:

  1. First, you generate the cryptographic key which you store into the Android Keystore system
  2. Then you use that key to encrypt and decrypt data

Okay, let’s get started.

Jetpack Security

Jetpack Security (JetSec) is a part of Android Jetpack. It provides abstractions for encrypting and decrypting SharedPreferences and Files. It also provides us with easy key management for the Android Keystore system.

To use JetSec in our application you need to include it in your project first. Add this to your app-module build.gradle file:

implementation("androidx.security:security-crypto:1.1.0-alpha03")

You must also add the Google Maven repository to your project. The 1.1.0 version provides support for devices that run Android 5.0 (API level 21) and higher. But I should mention that on Android 5.0 (API level 21) and Android 5.1 (API level 22), you cannot use the Android Keystore to store keysets.

Key management

To encrypt or decrypt a message, you first need to generate a key for the cryptographic operations. To do that, you will use the MasterKey class. Masterkey will also take care of the key management as it saves the key in the Android Keystore system.

The Android Keystore system lets you store cryptographic keys in a container to make it more difficult to extract from the device. Once keys are in the keystore, they can be used for cryptographic operations with the key material remaining non-exportable.”

To generate the key with default parameters, try doing this: 

MasterKey.Builder(this).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()

If you chose AES256_GCM for the KeyScheme, these will be the default parameters for the key generation:

KeyGenParameterSpec.Builder(
            mKeyAlias,
            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
            .setKeySize(DEFAULT_AES_GCM_MASTER_KEY_SIZE)

If you need to further configure your key, you can use KeyGenParameterSpec to make it specific for the use case. 

Let me introduce you to two additional options for increasing key security:

  1. Allow the key to be stored on a separate hardware module – this will improve security, but unfortunately, it comes with a performance drawback. Also, please note that you can apply it only to devices running API 28, or higher. And don't forget to set setRequestStrongBoxBacked to "true". Otherwise, it won't apply.
  2. Restrict key usage until the user authenticates via biometrics. Set setUserAuthenticationRequired to "true" to enable this option.

If you choose to increase the security of your application with user authentication, learn how to do it below. 

Biometric prompt

After you set the option setUserAuthenticationRequired to “true”, you can use BiometricPrompt for user authentication (switch to the feature/biometrics branch in the project to see this feature).

Now, place the function that requires the key (in our example, that’s the lambda function onSuccess()) inside of the AuthenticationCallback in the onAuthenticationSucceeded:

BiometricPrompt(this, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
onSuccess()
}
})

And change the key generation to:

MasterKey.Builder(this).apply {
setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
setUserAuthenticationRequired(true, 10)
}.build()

The second parameter in setUserAuthenticationRequired is optional and it’s the number of seconds that the key should remain accessible for following user authentication. If not previously set, it will, by default, last 300 seconds.

In case your user doesn’t authenticate, and you try to use the key, this will be the outcome:

android.security.keystore.UserNotAuthenticatedException: User not authenticated

Encrypt files

Now, let’s finally get to data encrypting and decrypting. First, you need to create an EncryptedFile object:

private val encryptedFile = EncryptedFile.Builder(
App.instance,
File(App.instance.filesDir, FILE_NAME),
masterKey,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

To write into the file, use openFileOutput:

encryptedFile.openFileOutput().apply {
write(password.toByteArray(StandardCharsets.UTF_8))
flush()
close()
}

Using the Device File Explorer, you can explore the file content and check if the encryption was successful. The path should look something like this: data/data/projectName/files/FILE_NAME.

To read from the encrypted file, use the following:

fun getPassword(): String {
var password = EMPTY_STRING
try {
val bufferReader = encryptedFile.openFileInput().bufferedReader()
password = bufferReader.readText()
bufferReader.close()
} catch (exception: IOException) {
Log.d(LOG_EXCEPTION_TAG, exception.message ?: "")
}
return password
}

In case the file doesn’t exist, you will get an exception and the empty string will return.

Encrypt SharedPreferences

If you need to store smaller data securely, you can use EncryptedSharedPreferences.

Using EncryptedSharedPreferences is really easy and after you initialise the EncryptedSharedPreferences, the approach is the same as with SharedPreferences, so let’s get started.

To create EncryptedSharedPreferences, use:

private val encryptedSharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
App.instance,
SHARED_PREFS_FILENAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

As you can see, EncryptedSharedPreferences.create() returns an instance of SharedPreferences
And now to encrypt and decrypt data, simply use the Editor interface:

fun savePassword(password: String) {
encryptedSharedPreferences.edit().putString(PASSWORD_KEY, password).apply()
}
fun getPassword(): String {
return encryptedSharedPreferences.getString(PASSWORD_KEY, EMPTY_STRING) ?: EMPTY_STRING
}

If you check the XML file, from the SharedPreferences, using the Device File Explorer, you’ll see that the value is encrypted. This is what I’ve received for using ‘123456789’ as input:

<string name="ARgMHjK+bYJVW3Cw0HL4QStkgTYBEpozQU2uAqQ=">AUbjoL+JzndtuR5ijEgscWBN1Juy4wXRG5vm4DmNPB44jHbuu5ut+WbjUjsgNEoP9cw=</string>

Wrap-up & more resources

Click here to see Jetpack security in a sample app I’ve built.

The sample app covers encrypting and decrypting data with EncryptedFile and EncryptedSharedPreferences. It uses MasterKey to generate and handle key management of the encryption/decryption key. Also, you can switch to the feature/biometrics branch to see how BiometricPrompt increases security.

Android is safe enough, but sometimes you want to use an extra level of security for the sensitive data like passwords, API keys or whatever you define as sensitive in your app.

Jetpack security provides out-of-the-box security for general purposes, but also allows you to customize and enhance your security, if needed, with adding StrongBox or specifying parameters for key generation. It’s simple to use and in just a few lines of code you can additionally secure your data. 

If you run into any questions, feel free to reach out. Good luck!

Like what you just read?

Go on, spread the news!

About the author

Damir is an Android Developer. In his spare time, he likes to read about mobile development and hang out with his friends.

Damir

Android Development

Write
Damir
Write COBE
Related Stories

This was interesting to you? Check these out.