» » Ensuring security in Android applications

Ensuring security in Android applications

Security is undoubtedly an important part of any mobile application. Unfortunately, providing real security is not an easy task. Google recently released the "security-crypto" library as part of the Jetpack components to make it easier to develop secure applications.

At the moment, the library consists of three main parts:

MasterKeys
EncryptedSharedPreferences
EncryptedFile


Ensuring security in Android applications

Internally, AndroidX Security uses the Tink library . Tink is an open source library from Google that provides cryptographic APIs.

Installation


To use the library, you need to add just one line to your application's Gradle file:

implementation `androidx.security:security-crypto:1.0.0-beta01`


Security-crypto is currently in beta, but you can check the mvnrepository page for new versions. It is really very small - after adding the library, the size of the .apk file increased by only 11.9 KB.
To use the library, you need to set minSdkVersion 23+, because the new API made KeyStore operations more stable ( KeyPairGeneratorSpec was replaced with KeyGenParameterSpec in API 23 ).

MasterKeys


MasterKeys is a helper class with just one public getOrCreate(...) method. It allows you 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, KeyGenParameterSpec, allows you to specify settings such as the algorithm, encryption mode, alignment, and key size. The default value assigned to 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 for SharedPreferences. It allows you to save and read values, while encrypting and decrypting data occurs already inside it. Let's see how its instance is created:

EncryptedSharedPreferences.create(
    PREFS_FILENAME,
    masterKeyAlias,
    applicationContext,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)


The second parameter is an alias generated by the MasterKeys.getOrCreate(KeyGenParameterSpec) method. The last two parameters specify the schemes used to encrypt keys and values. Currently the only possible value for EncryptedSharedPreferences.PrefKeyEncryptionScheme is AES256_SIV and for EncryptedSharedPreferences.PrefValueEncryptionScheme is AES256_GCM.
After that, we can use it like a normal SharedPreferences object. For example, saving a String looks like this:

encryptedPrefs.edit {
    putString(ENC_KEY, value)
    apply()
}


And so reading the stored value:

val string = encryptedPrefs.getString(ENC_KEY, null)


EncryptedFile


EncryptedFile makes it easy to encrypt data using a FileInputStream and decrypt it using a FileOutputStream. To create an EncryptedFile instance, we need to do the following:

EncryptedFile.Builder(
    file,
    applicationContext,
    masterKeyAlias,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()


The first parameter is a File object that specifies the path and name of the encrypted data file. In my case, the code looks like this:

val file = File(filesDir, ENCRYPTED_FILE_NAME)


The value of masterKeyAlias ​​is set in exactly the same way as for EncryptedSharedPreferences. The last parameter, fileEncryptionScheme, specifies the I/O stream encryption scheme. At the moment, the only possible value is AES256_GCM_HKDF_4KB. Here are the characteristics of this scheme:

- Primary key size: 32 bytes
- HKDF Algorithm: HMAC-SHA256
- AES-GCM derived key size: 32 bytes
- Ciphertext segment size: 4096 bytes

To illustrate, I decided to load the README.md file using the OkHttp library from the example repository (link here ), read the response bytes from response.body!!.Bytes(), and then save the file by passing the bytes to the 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 read them:

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 read the downloaded file, I called:

readFile { encryptedFile.openFileInput() }


To make sure the data is unreadable without decryption, let's load the file like this:

readFile { file.inputStream() }


Code samples


Code samples from the article can be found in the repository at the link.

Conclusions


AndroidX Security cleverly hides the implementation of complex security logic while providing developers with simple interfaces. With this library and a few lines of code, you can make your application more secure and avoid the situation where the developer forgets to configure something important. The only downside is that the developed app will only be compatible with Android Marshmallow and later. However, given the situation with KeyGenParameterSpec and KeyPairGeneratorSpec, this is a perfectly reasonable compromise. In addition, according to statistics from Google, only a quarter of the world's devices run Android 5 or earlier, and the number is steadily decreasing. That being said, don't forget that encrypting SharedPreferences and files in a project is just one of many factors that make an application truly secure. Let's hope that a stable version of the library will be released by Google soon.

Related Articles

Add Your Comment

reload, if the code cannot be seen

All comments will be moderated before being published.