Tuesday 29 December 2020

Getting KeyStoreException and GeneralSecurityException by using EncryptedSharedPreferences, how can I solve those?

Background

On one of the apps I work on, I store important stuff (tokens) into EncryptedSharedPreferences (taken from here and here):

/** a hardware-encrypted based shared preference (for the values).
 * Note that it is a bit slow, so it's better to always use it in a background thread.
 * Also, avoid having it being backed-up in the manifest, as it's hardware based and will become useless: https://stackoverflow.com/a/63795282/878126*/
object SecuredSharedPreferences {
    private var cachedDefaultSharedPreferences: SharedPreferences? = null

    /**warning: using this function can take some time (249 ms on Pixel 4, for example). Very recommended to avoid calling it on UI thread */
    @WorkerThread
    fun getDefaultSecuredSharedPreferences(context: Context): SharedPreferences {
        if (cachedDefaultSharedPreferences != null)
            return cachedDefaultSharedPreferences!!
        synchronized(this) {
            if (cachedDefaultSharedPreferences != null)
                return cachedDefaultSharedPreferences!!
            cachedDefaultSharedPreferences = getSecuredSharedPreferences(context, context.packageName + "_secured_preferences")
        }
        return cachedDefaultSharedPreferences!!
    }

    @WorkerThread
    private fun getSecuredSharedPreferences(context: Context, fileName: String): SharedPreferences {
        val masterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
        return EncryptedSharedPreferences.create(context, fileName, masterKey,
                EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
        )
    }
}

gradle:

implementation 'androidx.security:security-crypto:1.1.0-alpha03'

The problem

I've noticed 2 bugs being reported via Crashlytics when using this code (reported here):

  1. First one is of the MasterKey.Builder line of GeneralSecurityException :
Fatal Exception: java.security.GeneralSecurityException: Keystore operation failed
       at androidx.security.crypto.MasterKeys.generateKey(MasterKeys.java:146)
       at androidx.security.crypto.MasterKeys.getOrCreate(MasterKeys.java:97)
       at androidx.security.crypto.MasterKey$Builder.buildOnM(MasterKey.java:357)
       at androidx.security.crypto.MasterKey$Builder.build(MasterKey.java:314)
...
Caused by java.security.ProviderException: Keystore operation failed
       at android.security.keystore.AndroidKeyStoreKeyGeneratorSpi.engineGenerateKey(AndroidKeyStoreKeyGeneratorSpi.java:372)
       at javax.crypto.KeyGenerator.generateKey(KeyGenerator.java:612)
       at androidx.security.crypto.MasterKeys.generateKey(MasterKeys.java:142)
       at androidx.security.crypto.MasterKeys.getOrCreate(MasterKeys.java:97)
       at androidx.security.crypto.MasterKey$Builder.buildOnM(MasterKey.java:357)
       at androidx.security.crypto.MasterKey$Builder.build(MasterKey.java:314)
  1. Second one is on EncryptedSharedPreferences.create line of KeyStoreException, and occurs more often and for more users :
Fatal Exception: java.security.KeyStoreException: the master key android-keystore://_androidx_security_master_key_ exists but is unusable
       at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.readOrGenerateNewMasterKey(AndroidKeysetManager.java:275)
       at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.build(AndroidKeysetManager.java:236)
       at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:155)
       at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:120)
...
Caused by java.security.UnrecoverableKeyException: Failed to obtain information about key
       at android.security.keystore.AndroidKeyStoreProvider.loadAndroidKeyStoreSecretKeyFromKeystore(AndroidKeyStoreProvider.java:282)
       at android.security.keystore.AndroidKeyStoreSpi.engineGetKey(AndroidKeyStoreSpi.java:98)
       at java.security.KeyStore.getKey(KeyStore.java:825)
       at com.google.crypto.tink.integration.android.AndroidKeystoreAesGcm.<init>(AndroidKeystoreAesGcm.java:58)
       at com.google.crypto.tink.integration.android.AndroidKeystoreKmsClient.getAead(AndroidKeystoreKmsClient.java:164)
       at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.readOrGenerateNewMasterKey(AndroidKeysetManager.java:267)
       at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.build(AndroidKeysetManager.java:236)
       at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:155)
       at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:120)

What I've tried

Searching the Internet, I've found clues only for the first exception (GeneralSecurityException), that it might be that it occurs for custom ROMs, as they might not implement well the hardware keys for encryption.

And indeed, looking at the devices on Crashlytics, and looking at the Android version of each, I've found that they are ahead of what I see about the latest version that was supported for them.

For the second exception, sadly, I couldn't find any explanation and no solution either. I think it might be related to recovery of apps, but it's weird as it occurs quite often. On reddit (here), someone wrote that in case of such an exception, he chose to wrap the initialization of EncryptedSharedPreferences with "clear all data if fails" and bite the bullet. Also suggested it might be related to having android:allowBackup being disabled (and indeed it is).

The question

Why do these exceptions occur? What can I do against them?

Is clear-data the only thing that can be done? I'm not even sure it really helps, because if I choose to have it, it means the crash report will be gone each time it's about to happen...

Is it related to android:allowBackup being disabled?



from Getting KeyStoreException and GeneralSecurityException by using EncryptedSharedPreferences, how can I solve those?

No comments:

Post a Comment