Android Symmetric Encryption using Android KeyStore

In this post I will show you a simple class created to utilize symmetric encryption in Android using the KeyStore.

Encryption Difference in a nutshell:

  1. Symmetric encryption is where the same key is used to encrypt and decrypt data
  2. Asymmetric encryption is where two keys are generated, public and private key sets. The public key is handed to clients and the private key is located on the host.
    • The client uses the public key to encrypt data that is sent to the host.
    • The host then decrypts data with the private key.

This example uses symmetric encryption in the Android system.

I did not create a UI, the work in done and viewed in the logcat. It would be easy enough for anyone to create the UI, I just wanted to show the implementation and how to call the functions.

This project was derived from the project and explanation in the link below. There is more detailed information there so I would advice to go and read for complete understanding of the implementation.

https://proandroiddev.com/shedding-light-on-android-encryption-android-crypto-api-part-3-android-keystore-0054fb386a98

Code

I created a new file name KeystoreUtils. Put this inside.

class KeystoreUtils {

    private val ALIAS : String = "my_secret"

    private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply {
        load(null)
    }

    private val encryptCipher = Cipher.getInstance(TRANSFORMATION).apply {
        init(KeyProperties.PURPOSE_ENCRYPT, getKey())
    }

    private fun decryptCipherForIV() : Cipher {
        return Cipher.getInstance(TRANSFORMATION)
    }


    private fun createKey() : SecretKey {
        Log.d("Testing Keystore", "Creating key...")
        return KeyGenerator.getInstance(ALGORITHM).apply {
            init(
                KeyGenParameterSpec.Builder(
                    ALIAS,
                    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
                )
                    .setBlockModes(BLOCK_MODE)
                    .setEncryptionPaddings(PADDING)
                    .setUserAuthenticationRequired(false)
                    .setRandomizedEncryptionRequired(true)
                    .build()
            )
        }.generateKey()
    }

    private fun getKey() : SecretKey {
        val existingKey = keyStore.getEntry(ALIAS, null) as? KeyStore.SecretKeyEntry
        return existingKey?.secretKey ?: createKey()
    }

    fun encrypt(byteArray: ByteArray) : String {
        val encryptedBytes = encryptCipher.doFinal(byteArray)
        val iv = encryptCipher.iv

        val encryptedBytesWithIV = ByteArray(iv.size + encryptedBytes.size)
        System.arraycopy(iv, 0, encryptedBytesWithIV, 0, iv.size)
        System.arraycopy(encryptedBytes, 0, encryptedBytesWithIV, iv.size, encryptedBytes.size)
        return Base64.encodeToString(encryptedBytesWithIV, Base64.DEFAULT)
    }

    fun decryptWithIV(data: String) : String {
        val encryptedBytesWithIV = Base64.decode(data, Base64.DEFAULT)
        val cipher = decryptCipherForIV()
        val iv = encryptedBytesWithIV.copyOfRange(0, cipher.blockSize)
        cipher.apply {
            init(KeyProperties.PURPOSE_DECRYPT, getKey(), IvParameterSpec(iv))
        }
        val encryptedBytes = encryptedBytesWithIV.copyOfRange(cipher.blockSize, encryptedBytesWithIV.size)
        val decryptedBytes = cipher.doFinal(encryptedBytes)
        return String(decryptedBytes, Charsets.UTF_8)
    }

    companion object {
        private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
        private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC
        private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
        private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING"
    }
}

Then to use the class, I just called it from a LaunchEffect from the MainActivity. Not best practice but just to show proof of concept.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SampleAndroidKeystoreTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->

                    LaunchedEffect(Unit) {
                        val message = "{\"message\":\"This dog is amazing!\",\"id\":25,\"data\":\"Watch spot run!\"}"
                        val keystoreUtils = KeystoreUtils()
                        val encryptedMsg = keystoreUtils.encrypt(message.encodeToByteArray())
                        Log.d("Testing Keystore", " Message : $message")
                        Log.d("Testing Keystore", " Message : $encryptedMsg")

                        val decryptedMsg = keystoreUtils.decryptWithIV(encryptedMsg)
                        Log.d("Testing Keystore", " Message : $decryptedMsg")
                    }
                }
            }
        }
    }
}

Hope this help.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *