Previous posts in this series
We talked about the Android KeyStore system in brief in the last post. We will delve into more details about the KeyStore and how to use it for generating and storing keys from your application in this post.
To review: The keys that are generated using the KeyStore is not available outside of the Android Keystore. Your application only works with the references to these keys. The cryptographic operations are carried out by the Android Keystore.
To use the Android Keystore, you need to use the standard KeyStore APIs along with either the KeyPairGenerator or the KeyGenerator classes.
KeyGenerator provides the functionality of a secret (symmetric) key generator.
For example, if you want to persist username and password of a user in the SharedPreferences, you can safely use symmetric keys. However, there could be situations where you need to use asymmetric keys for enhanced security. KeyPairGenerator provides the APIs to generate a private-public key pair.
Do remember that, asymmetric cryptography operations take longer time in comparison to symmetric cryptography operations.
Let’s first see how to use the KeyGenerator class, to generate a symmetric key, and use the key for encryption and decryption.
1. Initializing the keystore
private fun initializeKeystore(): KeyStore{
// There could be many keystore providers.
// We are interested in AndroidKeyStore
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
return ks
}
2. Generating a symmetric key
fun generateSymmetricKey(alias: String) {
// Specify the algorithm to be used
val generator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
// Configurations for the key
val generatorSpec = KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
generator.init(generatorSpec.build())
// Generate the key
generator.generateKey()
}
3. Retrieving the generated key
// Retrieve the reference to the key by passing the same alias
// that was used while creating
fun getAsymmetricKey(alias: String): SecretKey {
return keystore.getKey(alias, CharArray(0)) as SecretKey
}
4. Cryptographic operations
4.1. Encryption
fun encryptDataAsymmetric(alias: String, data: String): String {
var key = getAsymmetricKey(alias)
var plainTextByteArray = data.toByteArray(Charset.defaultCharset())
var cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
cipher.init(Cipher.ENCRYPT_MODE, key)
var cipherText = cipher.doFinal(plainTextByteArray)
// IV needs to be preserved which will be used during decryption
// Encode cipher text and the iv to Base64 format
// Concatenate both strings separated by a comma(,)
return Base64.getEncoder()
.encodeToString(cipherText) +
"," +
Base64.getEncoder().encodeToString(cipher.iv)
}
What’s an IV?
IV is “Initialization Vector”. It is an arbitrary string that is used with a secret key for data encryption. It can also be compared with nonce
if you are familiar with this term. An IV is supposed to be used once.
If the same IV is used for all encryptions, the result will always be the same for same inputs. For example, “TechDroid” will always be encrypted to the same cipher text if we do not provide an IV or even use the same IV for all encryptions. Providing a different IV each
In the above example, an IV is generated for you if you do not provide one. Thus, it is important that you preserve it along with the cipher text. Without the IV that was used during encryption, we will not be able to decrypt the cipher text later.
IV’s are not supposed to be private. It can be saved in plaintext or transmitted to other systems without any encryption.
In this example, we are concatenating the cipher text and the generated IV, separated by a comma(,). While decrypting, we will separately use the cipher text and the IV.
4.2. Decryption
fun decryptDataAsymmetric(alias: String, data: String): String {
var key = getAsymmetricKey(alias)
// Extract the cipher text and the IV
var parts = data.split(",")
// Base64 decode of cipher text
var plainTextByteArray = Base64.getDecoder().decode(parts[0])
// Base64 decode of the IV
var iv = Base64.getDecoder().decode(parts[1])
var cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
var cipherText = cipher.doFinal(plainTextByteArray)
return cipherText.toString(Charset.defaultCharset())
}
You can find the source code on Github.
https://github.com/coomar2841/android-security
Pingback: Android Security – Part I: Persisting user credentials – Techdroid