How to encrypt data safely on the device and use the AndroidKeyStore?

Data encryption is very important in every Android application. You must have some way to secure your data, your Android files, your shared preferences, the keys that are used in your application. No doubt, even after applying some encryption technique your data can be retrieved back by some professional but your aim should be how to make it harder for them to get the data by providing some extra layers of security in your application. In this blog, we will learn how to encrypt data safely on the device and use the AndroidKeyStore for storing keys in Android. So, let's get started.

Jetpack Security Library

At Google I/O 19, the Jetpack Security Library was introduced that allows us to easily encrypt data, files, and shared preferences. It provides strong security that balances great encryption and good performance. Also, for apps that require a hardware-backed Keystore, it provides maximum security. So, all we need to do is just use this library without thinking about the work that it does in the backend.

But the Android operating system is very secure and we have separate file-based encryption system, so why to use this Android Jetpack Security Library? There are various reasons for the same, some of these are:

  • If you are working on a rooted device then the file system is unlocked and the data is easily accessible by some attacker even though you have full disk encryption.
  • Other reason can be securing your keys or tokens in your application because you don't want your users to use these keys.

So, this Jetpack Security Library is used for encryption on disk and is made available for Android version 6.0 or higher. All you need to do is add this library in your build.gradle file.

dependencies {
    ...
    implementation 'androidx.security:security-crypto:1.0.0-alpha01'
}

Key Management

The keys that we use in our Android application must be secured because if we are not securing our Android keys then it can be used against us in some or the other way. So, to protect our keys from being used by someone else, we have something called Android Keystore System in Android. It protects your key material from some unauthorized use. So, in order to use it, you have to be authorized. It is hardware-backed that means it runs on a separate memory space on the device. So, even though your app has access to the key, your app doesn't know what the key material is and in this way, your key material gets secured. For API level 28 or higher, you can use a StrongBox Keymaster that resides in a hardware security module and it is an implementation of Keymaster HAL. The module has its own CPU and secure storage. So, it will provide an extra layer of encryption to your keys.

In Jetpack Security, we have a MasterKeys class that allows us to create a private key(by default, AES256 is used).

val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)

Here, we are using the block mode GCM_SPEC with no padding. If you want to encrypt a small data that is of the size of a key, then you don't need any padding or blocking. But when the data that is to be encrypted is longer than the size of a key, then we use padding and blocking.

It is not always compulsory to use 256-bit key or use GCM with no padding. You have other options available with you like setBlockModes(), setEncryptionPaddings, setKeySize(), setUserAuthenticationRequired(), setUnlockedDeviceRequired(), etc.

val someAdvanceSpec = KeyGenParameterSpec.Builder(
    "master_key",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
    setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    setKeySize(256)
    setUserAuthenticationRequired(true)
    setUserAuthenticationValidityDurationSeconds(30)
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES_P){//Android P or higher
        setUnlockedDeviceRequired(true)
    }
}.build()
val advKey = MasterKeys.getOrCreate(someAdvanceSpec)

You can also generate a new EC key pair by using the KeyPairGenerator API.

val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC,
        "AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).run {
    setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
    build()
}

kpg.initialize(parameterSpec)

val kp = kpg.generateKeyPair()

Now, we are having the key and we can use this key to do other stuff like we can use an encrypted file. Let's see how.

File Encryption

With the help of Jetpack Security Library, you can encrypt your files present in your app. It utilizes the Streaming AES to handle files of all sizes. All you need to do is create a file and then make this file as an encrypted file. After getting the encrypted file, if you want to write some data to your encrypted file then you can use the openFileOutput() method and if you want to read data from your encrypted file then you can use the openFileInput() method. Following is the code for the same:

val secretFile = File(filesDirectory, "super_secret")
val encryptedFile = EncryptedFile.Builder(
    secretFile,
    applicationContext,
    advancedKeyAlias,
    FileEncryptionScheme.AES256_GCM_HKDF_4KB)
    .setKeysetAlias("file_key") //this is optional
    .setKeysetPrefName("secret_shared_prefs") //this is optional
    .build()

...

encryptedFile.openFileOutput().use { outputStream ->
    //write data from your encrypted file
}

encryptedFile.openFileInput().use { inputStream ->
    //read file from your encrypted file
}

That's it. Everything is encrypted and decrypted under the hood and you need not worry about the working.

SharedPreferences Encryption

We store our data in SharedPreferences because it is easy to use and at the same time, it also becomes easy for the attackers to get the key and value from the SharedPreferences. So, we need to encrypt our SharedPrefernce data and this can be done with the help of EncryptedSharedPreferences that is available for Android 6.0 or higher.

To use an EncryptedSharedPreference, just create or retrieve a Master Key from the AndroidKeyStore:

val myMaterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)

After getting the Master Key, now initialize an instance of EncryptedSharedPreferences:

val mySharedPreferences = EncryptedSharedPreferences.create(
    "my_preference_file_name",
    myMasterKeyAlias,
    applicationContext,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, //for encrypting Keys
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ////for encrypting Values
)

At last, you can save the data and read the data from the EncryptedSharedPreferences as usual:

//save data
mySharedPreferences.edit()
    .putString("MY_DATA", saveText.text.toString())
    .apply()

//read data
val someValue = mySharedPreferences.getString("MY_DATA", "")

Closing notes

In this blog, we learned how to secure our keys with the help of AndroidKeyStore and with the help of these keys we can secure our files and SharedPreferences. If you want to have some advanced encryption then you can use Tink, which is a Google open-source library that is also used by Jetpack Security Library. It has cross-platform security that provides security for different Android versions and for different mobile devices. You can learn more on Tink from here.

Hope you learned something new today.

Have a look at our Android tutorials here.

Do share this blog with your fellow developers to spread the knowledge. You can read more blogs on Android on our blogging website.

Apply Now: MindOrks Android Online Course and Learn Advanced Android

Happy Learning :)

Team MindOrks!