Android Jetpack Security
Security is undoubtedly an important element in mobile applications, but unfortunately, doing everything correctly is a complex task. Recently, Google released its security-crypto library as part of jetpack components to ease the process of making apps more secure.
At the moment, the library includes three main features:
- MasterKeys
- EncryptedFile
AndroidX Security is using the Tink library under the hood. Tink is an open-source crypto library created by Google that provides cryptographic APIs.
Installation
To use this library, all you need to do is add this one line to the app’s Gradle file:
implementation `androidx.security:security-crypto:1.0.0-alpha02`
Security-crypto is currently in alpha, but you can monitor for new releases of it on the mvnrepository page. Its size is really small – after analyzing the compiled .apk
file it added around 11.9 KB
.
In order to use this library, you will need to set minSdkVersion
to 23+
– because of the use of the new API, Keystore operations are especially stable (KeyPairGeneratorSpec
has been replaced by API 23 KeyGenParameterSpec
).
MasterKeys
MasterKeys
is a helper class that contains one public method getOrCreate
which allows the developer to create a master key and then get an alias for it. Let’s analyze the code:
@NonNull
public static String getOrCreate(
@NonNull KeyGenParameterSpec keyGenParameterSpec)
throws GeneralSecurityException, IOException {
validate(keyGenParameterSpec);
if (!MasterKeys.keyExists(keyGenParameterSpec.getKeystoreAlias())) {
generateKey(keyGenParameterSpec);
}
return keyGenParameterSpec.getKeystoreAlias();
}
The only parameter, the KeyGenParameterSpec
specifies options like algorithm, block mode, padding, and key size. The default value placed under MasterKeys.AES256_GCM_SPEC
creates the following object:
@NonNull
private static KeyGenParameterSpec createAES256GCMKeyGenParameterSpec(
@NonNull String keyAlias) {
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
keyAlias,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(KEY_SIZE);
return builder.build();
}
Where keyAlias
is _androidx_security_master_key_
and KEY_SIZE = 256
.
EncryptedSharedPreferences
EncryptedSharedPreferences
is a wrapper class of SharedPreferences
that allows you to save and read the values that are encrypting and decrypting everything under the hood. Let’s see how to create an instance of it:
EncryptedSharedPreferences.create(
PREFS_FILENAME,
masterKeyAlias,
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
The second parameter is an alias created using MasterKeys.getOrCreate(KeyGenParameterSpec)
and the last two define the schemes used for encryption keys and values. At the moment, the only available EncryptedSharedPreferences.PrefKeyEncryptionScheme
is AES256_SIV
and the only available EncryptedSharedPreferences.PrefValueEncryptionScheme
is AES256_GCM
.
And then we are able to use it just like a typical SharedPreferences
object. Here is an example of saving a String
:
encryptedPrefs.edit {
putString(ENC_KEY, value)
apply()
}
And reading the saved value:
val string = encryptedPrefs.getString(ENC_KEY, null)
Here is a quick video showing how it works in my example:
EncryptedFile
EncryptedFile
allows you to easily encrypt data using FileInputStream
and decrypt using FileOutputStream
. To create an instance of it, we need several things:
EncryptedFile.Builder(
file,
applicationContext,
masterKeyAlias,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()
The first parameter is a File
object that defines the path and name of where encrypted data should be stored. In my example, I created an instance of it like this:
val file = File(filesDir, ENCRYPTED_FILE_NAME)
Using masterKeyAlias
is exactly the same as in the EncryptedSharedPreferences
example. The last parameter, fileEncryptionScheme
defines the scheme of how the input/output stream should be encrypted or decrypted. At the moment, the only available value is AES256_GCM_HKDF_4KB
. Below, I have listed some details about it:
- Size of the main key: 32 bytes
- HKDF algo: HMAC-SHA256
- Size of AES-GCM derived keys: 32 bytes
- Ciphertext segment size: 4096 bytes
For example purposes, I decided to download the README.md
file from the example repository, accessible here using the OkHttp
library, getting bytes from the response response.body!!.bytes()
and then saving it by passing those bytes to following method:
private fun onFileDownloaded(bytes: ByteArray) {
var encryptedOutputStream: FileOutputStream? = null
try {
encryptedOutputStream = encryptedFile.openFileOutput().apply {
write(bytes)
}
} catch (e: Exception) {
Log.e(„TAG”, „Could not open encrypted file”, e)
} finally {
encryptedOutputStream?.close()
}
}
And then reading it:
private fun readFile(fileInput: () -> FileInputStream) {
var fileInputStream: FileInputStream? = null
try {
fileInputStream = fileInput()
val reader = BufferedReader(InputStreamReader(fileInputStream))
val stringBuilder = StringBuilder()
reader.forEachLine { line -> stringBuilder.appendln(line) }
result.text = stringBuilder.toString()
} catch (e: Exception) {
Log.e(„TAG”, „Error occurred when reading file”, e)
} finally {
fileInputStream?.close()
}
}
To encrypt and load the downloaded file content, I called:
readFile { encryptedFile.openFileInput() }
To load the file without encrypting to ensure that data is not readable without decrypting:
readFile { file.inputStream() }
Here is a video from the example showing how it works:
Example project
The working code can be found in the following repository here.
Summary
AndroidX Security cleverly wraps complex security logic while exposing simple interfaces to developers. With this library and a few lines of code, it’s possible to make our application more secure and eliminate cases where the developer forgets about some crucial configuration. The only downside is that it forces the developer to make the application compatible with android Marshmallow and newer only, but keeping in mind the backstory about KeyGenParameterSpec
and KeyPairGeneratorSpec
, this decision is reasonable. Also, according to the distribution dashboard provided by Google, only a quarter of devices in the world use Android 5 or older and this number is drastically decreasing over time. We still need to remember that encrypting SharedPreferences
and files in a project are one of many factors that make the app truly secure. Let's keep our fingers crossed for a stable release of this library soon.
Photo by Chris Panas on Unsplash