Android: Decryption Process Is Taking Lot Of Time

- 1 answer

When we create the set of keys with following code:

val generator = KeyPairGenerator.getInstance(RSA)
val keyPair = generator.genKeyPair()

Then when we call following:

val cipher = Cipher.getInstance(RSA_TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, privateKey)

The init method execution is done within 0-2 milliseconds.

But when we try to create the key with following as we need to store the private key in keystore :

val keyPairGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore")


The same init method takes more than 35-40 milliseconds for execution. Also if we comment setEncryptionPaddings and setDigest the init method throws exception.

Expected Output: To be able to store the private key in KeyStore and execution of init method in 0-2 milliseconds as it does without provider generator.



AndroidKeyStore is always going to be significantly slower than in-process key operations.

What happens when you do an in-process key generation is a JNI call to the BoringSSL crypto library. No IPC calls, no context switches, etc. BoringSSL will make some syscalls to the kernel to get random bits (from /dev/random) and there's a small chance that could trigger some calls into the hardware true random number generator, but not much.

What happens when you do a key generation in AndroidKeyStore is (roughly; everything below the hardware binder call is implementation-dependent so you'd have to talk to your device maker to learn the details):

  • Your process makes a binder call to the Keystore process, which may spin up a thread to process your request.
  • Keystore gets some random bits from /dev/random.
  • Keystore makes a hardware binder call to the Keymaster HAL service.
  • The service formats the key generation request into a message and writes it to a character device node, which invokes a kernel driver.
  • The kernel driver copies the request into a buffer along with some control data and invokes the SMC instruction.
  • The processor suspends Linux and jumps into the secure monitor handler, which checks a bunch of stuff, then switches the processor into secure mode.
  • The trusted OS kernel starts executing, reads the control data from the transfer buffer, identifies the trusted app which is supposed to receive it and calls it.
  • The trusted app parses the request message and generates your key, obtaining necessary random bits by reading from the hardware true random number generator and securely mixing that entropy with the bits provided by keystore (from /dev/random).
  • The trusted app then encrypts the key with a hardware-bound key and writes the result into a response buffer and returns control to the trusted OS.
  • The trusted OS writes some control data and invokes the SMC instruction.
  • The processor suspends the trusted OS and jumps into the secure monitor handler, which checks a bunch of stuff, then switches the processor out of secure mode.
  • The Linux kernel starts executing and the kernel driver returns data through the character device node.
  • The HAL service reads the data from the character device node.
  • The HAL service parses data the and returns it via hardware binder.
  • Keystore receives the encrypted key bundle and writes it into a file (associated with the alias you provided).
  • Keystore generates a self-signed certificate and writes it into another file.
  • Keystore returns the result status via binder.

I think the performance of AndroidKeyStore could be improved, but fundamentally it has to do a lot more than an in-process key generation does, and all of this IPC takes time.

What you get in exchange for the additional time is much greater security. With in-process crypto, an attacker who compromises your app can get a copy of the private key and can then do anything they like with it. With AndroidKeyStore, an attacker who compromises your app may be able to use the key the same way your app could, but they can't extract it from the device so they can't use it anywhere else. Also, if you add some constraints on how it can be used (e.g. only when the user authenticates), then the attacker can't violate those constraints.

Those security guarantees hold even if the attacker compromises not just your app, but the keystore daemon, the HAL service, and even the Linux kernel itself. To actually extract the key, the attacker has to compromise the trusted app or trusted OS. That's not impossible (nothing is), but it's much, much harder.

For completeness, I should also mention KeyGenParameterSpec.Builder.setIsStrongBoxBacked(true), available in API level 28. On devices that support StrongBox (currently not many, but this will change) your key won't be generated in a trusted OS running in the secure mode of the main CPU, it will be generated in a purpose-built security processor -- an embedded Secure Element, or similar -- called a "StrongBox". StrongBoxes must share no processor, RAM or other significant resources with the main CPU, and must be formally evaluated for security against direct penetration, side channels, glitching and more by an accredited testing laboratory.

StrongBox devices are typically much smaller, slower processors than mobile CPUs. Usually around two orders of magnitude slower in raw speed, though they partially offset that with dedicated crypto accelerator hardware. This means that if you use KeyGenParameterSpec.Builder.setIsStrongBoxBacked(true) you can expect not 40 ms, but 400 ms, or maybe 1000 ms. On the other hand, extracting any secrets from StrongBox devices is extraordinarily difficult. National intelligence agencies can probably do it, with effort, if they care enough. Anyone less capable than that is likely to have a very tough time indeed.

(As an aside: If you want to improve performance, you should consider dumping old, slow RSA. EC is much faster. Unfortunately, if you need asymmetric encryption, AndroidKeyStore doesn't yet support encryption/decryption with EC, only signing/verification. However, if you can get by with symmetric crypto, AES and HMAC are much faster than both EC and RSA.)

(Aside: I'm the Google engineer who has been responsible for AndroidKeyStore since API level 23.)