Android Data Encryption using Jetpack Security

Photo by Philipp Katzenberger on Unsplash

As an Android developer, you probably want to encrypt your application data at some points for security reasons. Those sensitive data can be varied from personal identifiable information (PII), financial records, to enterprise-related data.

By using Jetpack Security (JetSec), you can easily encrypt Files and SharePreferences locally to protect your sensitive information.

Include JetSec into the project

dependencies {
implementation "androidx.security:security-crypto:1.0.0-rc03"
}

Key Generation

  • Keyset: contains all the keys to encrypt files or shared preferences data
  • Master Key: the key to encrypt all keyset items

Before doing any cryptographic operation, you have to generate the key first.

Out of the box, JetSec provides the default master key within the MasterKey class with the AES256_GCM_SPEC specification. The master key is generated and stored in the Android Keystore system, which makes it difficult to extract the key material.

val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)

The default master key should be fine for general purposes. In case you want to create a master key with a custom specification, JetSpec also supports that:

val customSpec = KeyGenParameterSpec.Builder(
"master_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(60)
.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
setUnlockedDeviceRequired(true)
setIsStrongBoxBacked(true)
}
}
.build()

val masterKeyAlias = MasterKeys.getOrCreate(customSpec)

EncryptedSharedPreferences

Using EncryptedSharedPreferences, both keys and values are encrypted:

  • Keys: encrypted with deterministic encryption so that it can be looked up
  • Values: encrypted using AES-256 GCM and not deterministic

The below code snippets show you how to edit a record of the encrypted shared preferences:

val sharedPrefs: SharedPreferences = EncryptedSharedPreferences.create(
"secured_shared_prefs",
masterKeyAlias,
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

sharedPrefs.edit()
.putString("auth_token", "random_auth_token")
.apply()

And the result 🎉

EncryptedFile

To provide secure read and write operations from file streams, JetSec uses the Streaming Authenticated Encryption with Associated Data (AEAD) primitive. The data is divided into chunks and encrypted using AES256-GCM.

The below code snippets show you how to write data into file securely:

val fileToWrite = File(getDir("sensitive_data", MODE_PRIVATE), "encrypted_data.txt")
val encryptedFile = EncryptedFile.Builder(
fileToWrite,
applicationContext,
masterKeyAlias,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val fileContent = "Hello world!"
.toByteArray(StandardCharsets.UTF_8)

encryptedFile
.openFileOutput()
.run {
write(fileContent)
flush()
close()
}

… and read encrypted data from a file securely:

val fileToRead = File(getDir("sensitive_data", MODE_PRIVATE), "encrypted_data.txt")
val encryptedFile = EncryptedFile.Builder(
fileToRead,
applicationContext,
masterKeyAlias,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

val byteArrayOutputStream = ByteArrayOutputStream()
encryptedFile.openFileInput().run {
var nextByte: Int = read()

while (nextByte != -1) {
byteArrayOutputStream.write(nextByte)
nextByte = read()
}

close()
}

val plaintext = String(byteArrayOutputStream.toByteArray())

And the result 🎉

If you have any feedback/corrections, please feel free to leave a comment below.

Original post on Iced Tea Labs.

Thanks for reading and happy coding! 💻

Glasses 👓 Geek 💻 Backpacker 🎒 Climber 🧗‍♂️