How to write thread-safe code for Android Keystore

Weidian Huang
3 min readSep 4, 2020

What is Android Keystore

The Android Keystore provides access to special secure hardware for storing cryptographic keys. From Android 6.0, the security of Android Keystore was significantly enhanced, because Android started to support hardware-backed secure storage, then key material may be bound to the secure hardware inside the device, e.g., Trusted Execution Environment (TEE), Secure Element (SE).

Why Android Keystore is not thread-safe

Android provides a set of common APIs and algorithms for applications to use Android Keystore. But these APIs are just a wrapper layer, the actual calculation and storage are done by the secure hardware. There is only one single hardware chip doing all these things, once multiple applications or multiple threads are trying to access the hardware at same time, conflicts will happen, and below exceptions may be thrown from Android Keystore at different stages of operations.

During key generate stage, it will throw:

java.security.ProviderException: Keystore operation failed
at android.security.keystore.AndroidKeyStoreKeyGeneratorSpi.engineGenerateKey(AndroidKeyStoreKeyGeneratorSpi.java:324)
at javax.crypto.KeyGenerator.generateKey(KeyGenerator.java:612)

During crypto calculation stage, it can throw:

javax.crypto.IllegalBlockSizeException
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:486)
at javax.crypto.Cipher.doFinal(Cipher.java:1502)

Solution

The solution for these exceptions are simple, we just need to make sure all Android Keystore operations are thread-safe. The simplest solution is, all the methods in your code to call generateKey(), generateKeyPair(),getKey(), and all crypto operations to Android Keystore should be tagged as synchronized method.

Is the latest Jetpack Security library thread-safe

Google has released the first version of security library on top of Android keystore. The latest version is:

androidx.security:security-crypto:1.0.0-rc03

It provides 2 simple but yet powerful classes for developers to store encrypted data to file system: EncryptedFile and EncryptedSharedPreferences.

Unfortunately, Google has clearly told us in this document,

Note: The methods in both the EncryptedFile class and the EncryptedSharedPreferences class aren't thread-safe.

If we don’t follow this, we will likely get the same crash as we use the Android Keystore directly, and someone has reported it here.

No matter when we create EncryptedFile or EncryptedSharedPreferences, or when we read and write data from them, we need to make sure they are thread-safe. Especially EncryptedFile is used to read and write big data with files, it required more time for Android keystore to do the operations. In another words, it has higher chance to have those exceptions.

This is an example how to avoid the issue when we create a encrypted shared preference:

@synchronized
fun createSharedPreferences() {
val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
prefs = EncryptedSharedPreferences.create(
"file-name",
masterKeyAlias,
context,
PrefKeyEncryptionScheme.AES256_SIV,
PrefValueEncryptionScheme.AES256_GCM)
}

Conclusion

Android Keystore operation is not thread-safe. No matter we use it directly or indirectly, please make sure all the operations are synchronized.

One more thing to mention is if you use Google’s Tink library with Android Keystore, it is also not thread-safe. Because Jetpack’s security library is just another wrapper of Tink library.

Update 26-11-2020

Android Keystore is implemented by the vendors of the phone. Some devices have design flaw in the Android Keystore, and can throw below exceptions out of expectation.

During key query stage, it may throw:

java.security.UnrecoverableKeyException: Failed to obtain information about key
at android.security.keystore.AndroidKeyStoreProvider.getKeyCharacteristics(AndroidKeyStoreProvider.java:234)
at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(AndroidKeyStoreProvider.java:356)
at android.security.keystore.AndroidKeyStoreSpi.engineGetKey(AndroidKeyStoreSpi.java:101)
at java.security.KeyStore.getKey(KeyStore.java:1062)

During crypto calculation stage, it can throw:

javax.crypto.AEADBadTagException
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:517)
at javax.crypto.Cipher.doFinal(Cipher.java:2055)

Once the error happens, it could be recovered soon, it could be recovered in a few hours or even a few days.

To be able to recover quickly by the application, it is suggested to delete the key and re-generate it again.

To reduce the errors, some of the data can be cached in memory if the data is not very sensitive.

--

--

Weidian Huang

Android developer likes to dig deeper into the issues