Encryption Tutorial For Android: Getting Started
Ever wondered how you can use data encryption to secure your private user data from hackers? Look no more, in this tutorial you’ll do just that! By Kolin Stürt.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Encryption Tutorial For Android: Getting Started
30 mins
- Getting Started
- Securing the foundations
- Using Permissions
- Limiting installation directories
- Securing User Data With a Password
- Creating a Key
- Adding an Initialization Vector
- Encrypting the Data
- Decrypting the Data
- Saving Encrypted Data
- Securing the SharedPreferences
- Using a Key From a Server
- Using the KeyStore
- Generating a New Random Key
- Encrypting the Data
- Decrypting to a Byte Array
- Testing the Example
- Where to Go From Here?
Using a Key From a Server
You’ve just completed a great real-world example, but a lot of apps require a good on-boarding experience. Showing a password screen in addition to a login screen might not be a good user experience. For requirements such as this, you have some options.
The first is to take advantage of a login password to derive the key. You can also have the server generate that key instead. The key would be unique, and transmitted securely once the user has authenticated with their credentials.
If you’re going the server route, it’s important to know that since the server generates the key, it has the capacity to decrypt data stored on the device. There is the potential for someone to leak the key.
If none of these solutions work for you, you can take advantage of device security to secure the app.
Using the KeyStore
Android M introduced the ability to work with an AES key using the KeyStore API. This has some added benefits. The KeyStore allows you to operate on a key without revealing it’s secret content. Only the object, and not the private data, is accessible from app space.
Generating a New Random Key
In the Encryption.kt file, add the following code to the keystoreTest
method to generate a random key. This time around, the KeyStore protects the key:
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") // 1
val keyGenParameterSpec = KeyGenParameterSpec.Builder("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
//.setUserAuthenticationRequired(true) // 2 requires lock screen, invalidated if lock screen is disabled
//.setUserAuthenticationValidityDurationSeconds(120) // 3 only available x seconds from password authentication. -1 requires finger print - every time
.setRandomizedEncryptionRequired(true) // 4 different ciphertext for same plaintext on each call
.build()
keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()
Here’s what’s going on inside the code:
- You created a
KeyGenerator
instance and set it to the “AndroidKeyStore” provider. - Optionally, you added
.setUserAuthenticationRequired(true)
requiring a lock screen to be set up. - You optionally added
.setUserAuthenticationValidityDurationSeconds(120)
so that the key is available 120 seconds after device authentication. - You called
.setRandomizedEncryptionRequired(true)
. This tells the KeyStore to use a new IV each time. As you learned earlier, that means that if you encrypt identical data a second time, the encrypted output will not be identical. It prevents attackers from obtaining clues about the encrypted data based on feeding in the same inputs.
There are a few other things about the KeyStore options you should know about:
- For
.setUserAuthenticationValidityDurationSeconds()
, you can pass -1 to require fingerprint authentication every time you want access to the key. - Enabling the screen lock requirements will revoke keys as soon as the user removes or changes the lock screen pin or passcode.
- Storing a key in the same place as the encrypted data is like putting a key under the doormat. The KeyStore tries to protect the key with strict permissions and kernel level code. On some devices, keys are hardware backed.
- You can use
.setUserAuthenticationValidWhileOnBody(boolean remainsValid)
. This makes the key unavailable once the device has detected it is no longer on the person.
Encrypting the Data
Now, you’ll make use of that key that’s stored in the KeyStore. In the Encryption.kt file, add the following to the keystoreEncrypt
method, right under the //TODO: Add code here
:
// 1
//Get the key
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val secretKeyEntry =
keyStore.getEntry("MyKeyAlias", null) as KeyStore.SecretKeyEntry
val secretKey = secretKeyEntry.secretKey
// 2
//Encrypt data
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val ivBytes = cipher.iv
val encryptedBytes = cipher.doFinal(dataToEncrypt)
// 3
map["iv"] = ivBytes
map["encrypted"] = encryptedBytes
Here:
- This time, you retrieve the key from the KeyStore.
- You encrypted the data using the
Cipher
object, given theSecretKey
. - Like before, you return a
HashMap
containing the encrypted data and IV needed to decrypt the data.
Decrypting to a Byte Array
Add the following to the keystoreDecrypt
method, right under the //TODO: Add code here
:
// 1
//Get the key
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val secretKeyEntry =
keyStore.getEntry("MyKeyAlias", null) as KeyStore.SecretKeyEntry
val secretKey = secretKeyEntry.secretKey
// 2
//Extract info from map
val encryptedBytes = map["encrypted"]
val ivBytes = map["iv"]
// 3
//Decrypt data
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, ivBytes)
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
decrypted = cipher.doFinal(encryptedBytes)
In this code, you:
- Obtained the key again from the KeyStore.
- Extracted the necessary info from
map
. - Initialized the
Cipher
object using theDECRYPT_MODE
constant and decrypted the data to aByteArray
.
Testing the Example
Now that you’ve created the ways to encrypt and decrypt data using the KeyStore API, it’s time to test them out. Add the following to the end of the keystoreTest
method:
// 1
val map = keystoreEncrypt("My very sensitive string!".toByteArray(Charsets.UTF_8))
// 2
val decryptedBytes = keystoreDecrypt(map)
decryptedBytes?.let {
val decryptedString = String(it, Charsets.UTF_8)
Log.e("MyApp", "The decrypted string is: $decryptedString")
}
In the updated code, you:
- Created a test
string
and encrypted it. - Called the
decrypt
method on the encrypted output to test that everything worked.
In the onCreate
method of the MainActivity.kt file, uncomment the line that reads //Encryption().keystoreTest()
. Build and run the app to check that it worked. You should see the decrypted string:
Where to Go From Here?
Congratulations, you’ve learned the ways of encrypting and decrypting data on Android!
You also learned other ways of working with keys using the Keystore. You can download the final project using the Download Materials button at the top or bottom of this tutorial, if you skipped some steps, to have the fully working project and all the code filled in.
It’s great to know how to properly implement security. Armed with this knowledge, you’ll be able to confirm if third-party security libraries are up to the best practices. However, implementing it all yourself, especially if rushed, can lead to mistakes. If you’re in that boat, consider using an industry approved or time-tested third party.
Conceal is a great choice for a third party encryption library. It gets you up and running without having to worry about the underlying details. The one drawback — when hackers expose a vulnerability in a popular library. This affects all the apps that rely on that third party at the same time. Apps that have a custom implementation are often immune to wide-spread, scripted attacks.
The Account Manager is part of the Android OS and has a corresponding API. It’s a centralized manager for user account credentials so your app doesn’t have to store or work with passwords and logins directly. The most well known example of this is when requesting an OAuth2 token.
Introduced in Android 4.0 (API Level 14), the Keychain API deals with key management. It specifically works with PrivateKey
and X509Certificate
objects and provides a more secure container than using your app’s data storage. You can use it to install certificates and use private key objects directly.
Your security code will work well to protect your app, as long as someone doesn’t tamper with it. Check out the ProGuard tutorial to learn how to help prevent reverse engineering or tampering with your security-related code.
Now that you’ve secured your data at rest, why not read about how to secure your data in transit? And for those of you looking for advanced cryptography, check out the GCM Mode for AES.
By the way, the sample data characters Esther, Cornelius, Lightning and Birgit are all real! :]
If you have any questions or comments on what you’ve learned, join the forum discussion below!