Skip to content
This repository has been archived by the owner on Apr 17, 2024. It is now read-only.

Bug: using EncryptedSharedPreferences, it can cause crashes to users right when initializing it #535

Closed
AndroidDeveloperLB opened this issue Aug 6, 2021 · 68 comments

Comments

@AndroidDeveloperLB
Copy link

This:
https://issuetracker.google.com/issues/176215143

I was told I should write about this here:
https://issuetracker.google.com/issues/185219606#comment4

@oswaldo89
Copy link

oswaldo89 commented Aug 16, 2021

any comment about this ?

I detected this issue occurs in ( Not on all devices ):

  • huawei P20
  • huawei Y6II
  • Samsung Galaxy S9
  • Galaxy Grand Prime Plus
  • Asus ZenFone Max Pro M1

the master key android-keystore://_androidx_security_master_key_ exists but is unusable

@thaidn
Copy link
Contributor

thaidn commented Aug 16, 2021

Do you have a stack trace? How frequently has this happened?

@AndroidDeveloperLB
Copy link
Author

@thaidn Check the original post. It has enough.

@oswaldo89
Copy link

Do you have a stack trace? How frequently has this happened?

@thaidn

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.(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)

@oswaldo89
Copy link

since we implemented the encrypted preferences, it happens very frequently, only to certain devices.

image

@Tharkius
Copy link

Tharkius commented Sep 2, 2021

Do we have any fix or workaround for this crash already? Currently we have on Firebase 23 reports of this exception:

Non-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)
       at wit.android.bcpBankingApp.utils.security.EncryptedKeyStorage.init(EncryptedKeyStorage.java:49)
       ...

The exceptions occur in these devices, mainly in Android 7 (57%) and 6 (26%):

Huawei Honor 8
Huawei Y6II
OPPO Find X3 Pro
wheatek BV6300Pro

Currently we are catching the exception, but by doing so, every method invoked to the EncryptedSharedPreferences instance afterwards will throw an NPE.

@jarrodrobins
Copy link

This is an incredibly frustrating issue and makes the latest versions of this library essentially useless. The workarounds I found were -

  1. Add
android:allowBackup="false"
android:fullBackupContent="false"

to your Application in AndroidManifest.xml. This prevents your encrypted files from getting backed up - EncryptedSharedPreferences can't open them on reinstall anyway.

  1. Don't use 1.1.0-alpha02 or 1.1.0-alpha03. From my experience, 1.1.0-alpha01 is the last working version.

One or the other may be enough, but this nasty issue popped up for me the day-of our first production release, so I threw the whole kitchen sink at it. As far as I can tell, the issue hasn't been seen since.

One other alternative I considered was just not using EncryptedSharedPreferences at all and just falling back to good ol' SharedPreferences. Obviously you lose the entire point of the library, but at least it appears to work more often than not.

@AndroidDeveloperLB
Copy link
Author

AndroidDeveloperLB commented Sep 2, 2021

@jarrodrobins Wait, can I avoid the flags of backup, and use the alpha01 and it should work fine? All of the crashes I've reported won't occur again?

@thaidn
Copy link
Contributor

thaidn commented Sep 2, 2021

Don't use 1.1.0-alpha02 or 1.1.0-alpha03. From my experience, 1.1.0-alpha01 is the last working version.

Wait, when you switched to 1.1.0-alpha01, the problem went away?

@Tharkius
Copy link

Tharkius commented Sep 2, 2021

@jarrodrobins Wait, can I avoid the flags of backup, and use the alpha02 and it should work fine? All of the crashes I've reported won't occur again?

We've had the app with allowBackUp as false for a long time before the crash showed up so that's not going to solve the problem.

These are very rare cases though and I'm tented to propose showing some dialog to the user explaining na critical error occurred. Because it is a critical error...

@jarrodrobins
Copy link

That was my experience with 1.1.0-alpha01 (NOT 02 or 03), yes. It may not be necessary to disable backups, but a number of other posts (eg StackOverflow) referenced that as a possible workaround.

The 'testing' I was doing involved finding a teammate who was able to replicate the issue and having them install a bunch of different test builds changing various bits and pieces as it was not something I could replicate locally. The working build for them involved both 'fixes' but yes, it's possible changing it to alpha01 is enough.

I implemented this workaround on my project a while ago so my memory is a little hazy on every single test variant I tried, so you might want to double check on your end if you want to try downgrading alone.

https://stackoverflow.com/questions/65463893/getting-keystoreexception-and-generalsecurityexception-by-using-encryptedsharedp

This post above references the backup fix. One person in there said alpha01 alone didn't solve their issue, for what it's worth.

@Tharkius
Copy link

Tharkius commented Sep 2, 2021

That was my experience with 1.1.0-alpha01 (NOT 02 or 03), yes. It may not be necessary to disable backups, but a number of other posts (eg StackOverflow) referenced that as a possible workaround.

The 'testing' I was doing involved finding a teammate who was able to replicate the issue and having them install a bunch of different test builds changing various bits and pieces as it was not something I could replicate locally. The working build for them involved both 'fixes' but yes, it's possible changing it to alpha01 is enough.

I implemented this workaround on my project a while ago so my memory is a little hazy on every single test variant I tried, so you might want to double check on your end if you want to try downgrading alone.

https://stackoverflow.com/questions/65463893/getting-keystoreexception-and-generalsecurityexception-by-using-encryptedsharedp

This post above references the backup fix. One person in there said alpha01 alone didn't solve their issue, for what it's worth.

The thing is, like I said, it's a very rare occasion, you could go for a long time without having any cases thinking you solved the issue.

@jarrodrobins
Copy link

While getting it to occur is very rare, once it does, it occurs 100% of the time following that. Like I said, I couldn't get it to occur locally, and relied on a user to help me work around it. All I know is those two fixes stopped that user from getting the crash (after getting them to install at least five other intermediate builds trying different things).

@AndroidDeveloperLB
Copy link
Author

But if you don't have the backup flags, would it still occur on alpha 02 ?

@Tharkius
Copy link

Tharkius commented Sep 3, 2021

But if you don't have the backup flags, would it still occur on alpha 02 ?

Yes... I wonder if someone over at Google could give us a hint.

@jarrodrobins
Copy link

¯_(ツ)_/¯

When I was testing it, alpha02 still caused the crash without the backup flags. But you may want to test it yourself if you're able to replicate it.

Ideally Google would just fix the damn thing, considering it's been a problem for at least a year that I'm aware of.

@Tharkius
Copy link

Tharkius commented Sep 3, 2021

¯_(ツ)_/¯

When I was testing it, alpha02 still caused the crash without the backup flags. But you may want to test it yourself if you're able to replicate it.

Ideally Google would just fix the damn thing, considering it's been a problem for at least a year that I'm aware of.

Ideally yeah. I mean, we use their IDE everyday and they have all our data...

@thaidn
Copy link
Contributor

thaidn commented Sep 3, 2021

While getting it to occur is very rare, once it does, it occurs 100% of the time following that. Like I said, I couldn't get it to occur locally, and relied on a user to help me work around it. All I know is those two fixes stopped that user from getting the crash (after getting them to install at least five other intermediate builds trying different things).

This is sorta expected.

First of all, I apologize for causing so many pains and frustrations. I made a wrong decision trusting Android Keystore which happens to be super unreliable, especially on exotic devices.

Let me explain what I think is happening, why there's little Google can do even though I'd spent days on this, and possible workarounds with caveats.

Background & root cause

The dependency chain is:

Users -> Jetpack Security -> Tink -> Android Keystore -> OEM firmware/hardware.

I work on Tink and contributed to Jetpack Security. I think the root cause of the crashes is neither in Tink nor Jetpack, but in Android Keystore and OEM firmware/hardware.

Shared prefs are encrypted with a Tink keyset (which is a protobuf). The keyset is encrypted with a master key stored in Android Keystore. The encrypted keyset itself is stored as a special value in the encrypted shared prefs file.

We've found that Android Keystore occasionally corrupts the master key on certain devices. We don't know why, we think it could be due to faulty OEM firmware/hardware.

When the master key is corrupted, Tink won't be able to decrypt, and return the error the master key android-keystore://_androidx_security_master_key_ exists but is unusable.

Workaround

There are two ways to recover from a corrupted master key:

1/ If you don't have any existing encrypted prefs, you can delete the master key (using deleteEntry()), and try again.

2/ If you have existing prefs, it's very likely that they will be lost forever. I'm so sorry!

To prevent further crashes, you can:

  • delete the master key, and
  • delete the encrypted prefs, and
  • delete the encrypted Tink keyset.

I'll talk to the Jetpack team to see if we can add a function to do this for you. In the meantime, you have to delete these things by yourself.

For example, if you use the following code:

    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
        )
    }

Then you want to delete the file fileName. If you only regenerate the master key, but don't delete existing data, you will get InvalidProtocolBufferException caused by Jetpack Security/Tink trying to use the new master key to decrypt its keyset.

@jarrodrobins
Copy link

Thanks for the really solid explanation @thaidn. I appreciate that this issue is across several teams and is hard for you guys to track down.

I'll give these workarounds a try. Thank you!

@AndroidDeveloperLB
Copy link
Author

@thaidn As you think that it's a hardware issue, can you please tell Google to add a test to licensing devices (I think it's called CTS), so that at least for new devices, this issue won't occur?

@Tharkius
Copy link

Tharkius commented Sep 3, 2021

While getting it to occur is very rare, once it does, it occurs 100% of the time following that. Like I said, I couldn't get it to occur locally, and relied on a user to help me work around it. All I know is those two fixes stopped that user from getting the crash (after getting them to install at least five other intermediate builds trying different things).

This is sorta expected.

First of all, I apologize for causing so many pains and frustrations. I made a wrong decision trusting Android Keystore which happens to be super unreliable, especially on exotic devices.

Let me explain what I think is happening, why there's little Google can do even though I'd spent days on this, and possible workarounds with caveats.

Background & root cause

The dependency chain is:

Users -> Jetpack Security -> Tink -> Android Keystore -> OEM firmware/hardware.

I work on Tink and contributed to Jetpack Security. I think the root cause of the crashes is neither in Tink nor Jetpack, but in Android Keystore and OEM firmware/hardware.

Shared prefs are encrypted with a Tink keyset (which is a protobuf). The keyset is encrypted with a master key stored in Android Keystore. The encrypted keyset itself is stored as a special value in the encrypted shared prefs file.

We've found that Android Keystore occasionally corrupts the master key on certain devices. We don't know why, we think it could be due to faulty OEM firmware/hardware.

When the master key is corrupted, Tink won't be able to decrypt, and return the error the master key android-keystore://_androidx_security_master_key_ exists but is unusable.

Workaround

There are two ways to recover from a corrupted master key:

1/ If you don't have any existing encrypted prefs, you can delete the master key (using deleteEntry()), and try again.

2/ If you have existing prefs, it's very likely that they will be lost forever. I'm so sorry!

To prevent further crashes, you can:

  • delete the master key, and
  • delete the encrypted prefs, and
  • delete the encrypted Tink keyset.

I'll talk to the Jetpack team to see if we can add a function to do this for you. In the meantime, you have to delete these things by yourself.

For example, if you use the following code:

    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
        )
    }

Then you want to delete the file fileName. If you only regenerate the master key, but don't delete existing data, you will get InvalidProtocolBufferException caused by Jetpack Security/Tink trying to use the new master key to decrypt its keyset.

Thanks for your time and information. I got a question though: to delete the master key would this code suffice?

try {
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());

    keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS);
} catch (KeyStoreException e) {
    Log.e(TAG, "Error occurred while trying to delete the master key", e);
}

@thaidn
Copy link
Contributor

thaidn commented Sep 3, 2021

You have to load the Android KeyStore.

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(/* param= */ null);
keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS);

@Tharkius
Copy link

Tharkius commented Sep 3, 2021

You have to load the Android KeyStore.

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(/* param= */ null);
keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS);

Hmmm. That can also crash 🤔

Oh well, it's our best bet anyway. Thanks for the help again.

@Tharkius
Copy link

Tharkius commented Sep 6, 2021

You have to load the Android KeyStore.

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(/* param= */ null);
keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS);

I'm using the following code in an attempt to reset the master key and all (the shared prefs file is also correctly being deleted, according to the log message):

try {
    KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
    File sharedPrefsFile = new File(
        context.getFilesDir().getParent() + "/shared_prefs/" + SHARED_PREFS_FILENAME + ".xml"
    );

    boolean deleted = sharedPrefsFile.delete();

    Log.d(TAG, "Shared prefs file deleted: %s", deleted);

    keyStore.load(null);
    keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS);
} catch (Exception e) {
    Log.e(TAG, "Error occurred", e);
}

But after running this, the next call to EncryptedSharedPreferences.create() fails with that protobuf exception you mentioned:

com.google.crypto.tink.shaded.protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero).
        at com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite.parsePartialFrom(GeneratedMessageLite.java:1566)
        at com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite.parseFrom(GeneratedMessageLite.java:1664)
        at com.google.crypto.tink.proto.Keyset.parseFrom(Keyset.java:957)
        at com.google.crypto.tink.integration.android.SharedPrefKeysetReader.read(SharedPrefKeysetReader.java:84)
        at com.google.crypto.tink.CleartextKeysetHandle.read(CleartextKeysetHandle.java:58)
        at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.read(AndroidKeysetManager.java:328)
        at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.readOrGenerateNewKeyset(AndroidKeysetManager.java:287)
        at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.build(AndroidKeysetManager.java:238)
        at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:155)
        at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:120)

Is there anything else I'm missing here?

@syedalattas
Copy link

Then you want to delete the file fileName. If you only regenerate the master key, but don't delete existing data, you will get InvalidProtocolBufferException caused by Jetpack Security/Tink trying to use the new master key to decrypt its keyset.

I could be wrong @Tharkius but i did something like this and I no longer get that exception.

// delete the master key
KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
keyStore.load(null)
keyStore.deleteEntry(MasterKey.DEFAULT_MASTER_KEY_ALIAS)

// delete the encrypted prefs
context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE).edit().clear().apply()

// create encrypted prefs

@AndroidDeveloperLB
Copy link
Author

Doesn't deleting the data ruin how the app might work, though ?
I mean, if you expect to see some content there, now it will be gone, no?

@Tharkius
Copy link

Tharkius commented Sep 7, 2021

Doesn't deleting the data ruin how the app might work, though ?
I mean, if you expect to see some content there, now it will be gone, no?

There's not much point in keeping data you can't decrypt anymore...

@AndroidDeveloperLB
Copy link
Author

So it's not a workaround to still use what you had before. It's resetting of the data you've saved, just to avoid a crash, no?

Suppose this is the code I use:

    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
        )
    }

What exactly should I change here for this workaround?

@juergw
Copy link
Contributor

juergw commented Apr 26, 2023

Also, please use the lastest version of androidx security crypto, 1.1.0-alpha06, https://developer.android.com/jetpack/androidx/releases/security#security-crypto-1.1.0-alpha06.

@jkdwivedi1992
Copy link

I am getting this after updating compiled and targeted sdk to 33
empty keyset java.security.GeneralSecurityException: empty keyset at com.google.crypto.tink.KeysetHandle.assertEnoughKeyMaterial(KeysetHandle.java:1001) at com.google.crypto.tink.KeysetHandle.fromKeyset(KeysetHandle.java:614) at com.google.crypto.tink.CleartextKeysetHandle.read(CleartextKeysetHandle.java:61) at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.readKeysetInCleartext(AndroidKeysetManager.java:269) at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.build(AndroidKeysetManager.java:295) at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:169) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at io.mockk.proxy.jvm.advice.MethodCall.call(MethodCall.kt:14)

@juergw
Copy link
Contributor

juergw commented Apr 26, 2023

This is weird. You are calling EncryptedSharedPreferences.create, which always uses a master key alias, but then you end up in a code path that is assumes that a master key can't be used, because the the android version is too old:
https://github.com/tink-crypto/tink-java/blob/1.8/src/main/java/com/google/crypto/tink/integration/android/AndroidKeysetManager.java#L295

That shouldn't happen for Android 33. What are the values of
android.os.Build.VERSION.SDK_INT and android.os.Build.VERSION_CODES.M?

@snti
Copy link

snti commented Apr 27, 2023

I had the same issue even with the EncryptedSharedPreferences.create wrapped in a sync block. I moved the entire method to a Singleton and I did some heavy test and the issue seems to be fixed. The Singleton doesn't hold any references, the EncryptedPreferences Creation always returns a new instance like I was doing before. Idk yet how this fixes my problem, but I guess is related to how instances are shared between threads.

To reproduce this error, you can make some loops with new threads doing operations with the encrypted preferences.

@juergw
Copy link
Contributor

juergw commented Jun 5, 2023

Can you provide a stack trace? From your description, it seems to be a bug in EncryptedSharedPreferences itself, and not in AndroidKeysetManager.

@filipatbnp
Copy link

same problem here

@jt-gilkeson
Copy link

jt-gilkeson commented Dec 29, 2023

This has worked for the majority of cases where there was an issue on phones that had issue when updating secuirty-cypto versions, but a few months ago I started seeing new cases where we get javax.crypto.AEADBadTagException android.security.KeyStoreException Signature/MAC verification failed (internal Keystore code: -30 . The Pixel 5 is one of the phones it seems to happen on. This is with androidx.security:security-crypto:1.1.0-alpha06

@deepakkanyan
Copy link

I am also geeting same carsh on firebase.

Caused by javax.crypto.AEADBadTagException:
       at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:632)
       at javax.crypto.Cipher.doFinal(Cipher.java:2114)
       at com.google.crypto.tink.integration.android.AndroidKeystoreAesGcm.decryptInternal(AndroidKeystoreAesGcm.java:1)
       at com.google.crypto.tink.integration.android.AndroidKeystoreAesGcm.b(AndroidKeystoreAesGcm.java:6)
       at com.google.crypto.tink.KeysetHandle.decrypt(KeysetHandle.java:43)
       at com.google.crypto.tink.KeysetHandle.readWithAssociatedData(KeysetHandle.java:43)
       at com.google.crypto.tink.KeysetHandle.read(KeysetHandle.java:43)
       at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.readMasterkeyDecryptAndParseKeyset(AndroidKeysetManager.java:27)
       at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.build(AndroidKeysetManager.java:1)
       at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:248)
       at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:248)
       at com.myapp.DatabaseModule.provideEncryptedSharedPrefs(DatabaseModule.java:248)
Caused by android.security.KeyStoreException
Signature/MAC verification failed (internal Keystore code: -30 message: system/security/keystore2/src/operation.rs:850: KeystoreOperation::finish Caused by: 0: system/security/keystore2/src/operation.rs:426: Finish failed. 1: Error::Km(r#VERIFICATION_FAILED))

I am using androidx.security:security-crypto:1.1.0-alpha06

compile and targetSdk. = 34

    <android:name= "App Name"
   android:allowBackup="false"
    android:dataExtractionRules="@xml/no_backup_rules"
    android:fullBackupContent="false"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme"
    tools:replace="android:allowBackup">

@juergw
Copy link
Contributor

juergw commented Jan 8, 2024

That you now may get a android.security.KeyStoreException instead of a InvalidProtocolBufferException is expected, I changed that in 592c2eb. This happens if the master key stored in android keystore is not able to decrypt the encrypted keyset used by EncryptedSharedPreferences.

The question is, why did you get into this state? There are two possibilities:
a) the encrypted keyset has been modified.
b) the current master key in keystore is not the same as the master key that was used to encrypt the keyset.

I think it is more likely that you are in situation b). This may have happen if you deleted the master key from keystore. The next time a "MasterKey" object is built, it will automatically create a new master key in keystore, which will not be able to decrypt the keyset encrypted with the deleted master key.

Another possibility is that there is a race-condition, where two threads at the same time create an EncryptedSharedPreferences. Both threads see that there is not yet a master key, and both create a new one, and in the end the encrypted keyset is not from the master key in keystore. Looking at the code, I don't think this race-condtion may happen if the EncryptedSharedPreferences is always created with the "MasterKey" object, since the key will always be created by the same function, and it uses a lock, see
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:security/security-crypto/src/main/java/androidx/security/crypto/MasterKeys.java;l=101

If, however, EncryptedSharedPreferences is created once with the MasterKey object in one thread, and in a different thread using the deprecated "create" function that uses a key alias, then there may be a race condition because the deprecated create function will let the AndroidKeysetManager create the master key using a different lock. We can't really fix this race condition without breaking the API. So please don't use the deprecated create function.

If there are other race conditions in this code that I don't know, I'd need a code example that shows the problem.

One way to make sure that you don't run into such a race-condition is to create a MasterKey object at the startup of your application, before you start any other threads. Then this MasterKey object will create the master key in keystore, and it will not be overwritten by any other thread.

@deepakkanyan
Copy link

I have updated the sdk code to 34 and update cryto lib from alpha-3 to alpha-6.

Then this crash is happening.

@juergw
Copy link
Contributor

juergw commented Jan 17, 2024

As I said, there are many reasons why you can't decrypt the master key. Without a detailed description on how we can reproduce the problem, there is nothing we can do.

The Tink Java library is now hosted on https://github.com/tink-crypto/tink-java. Please open an issue there if you have a way to reproduce the problem.

@juergw juergw closed this as completed Jan 17, 2024
amberin added a commit to amberin/orgzly-android-revived that referenced this issue Feb 13, 2024
Access to the master key that is used to wrap Ed25519 keys relies on
Android Keystore, Google Tink and androidx.security.crypto. We are using
alpha versions of the latter library, and it is notoriously flaky (see
e.g. tink-crypto/tink#535).

The main issue that caused me to go over this class was that on newly
created API 30 devices, a Ed25519 key with device lock protection could
never be used, even if they had just been created (i.e. the newly
created master key was probably not corrupted).

I realized that *something* had changed so that we now need to
authenticate in order to just create the private key object. This has
not been the case in my previous tests; authentication was not required
until we tried to _use_ they key to do something (in our case, sign an
arbitrary string).

Since we need to authenticate in more situations than before, I added a
function which can be called whenever we catch a
UserNotAuthenticatedException.

Concrete changes:

- Upgrade androidx.security.crypto from 1.1.0-alpha06 (which relies on a
  newer version of Google Tink, which is hopefully more stable). Note
  that this requires compiling against SDK >= 33, but I believe that
  should be fine.
- When preparing to use a Ed25519 key for signing, use a signing
  algorithm which actually works with those keys. This has been causing
  an InvalidKeyException during each use of a Ed25519 for a long time.
- Catch all UserNotAuthenticatedExceptions and try biometric auth when
  they occur.
- Remove the mustAuthenticate attribute, as it becomes useless now that
  we rely on catching UserNotAuthenticatedExceptions.
- Make privateKeyLoadAttempts a class attribute which can be updated
  from many places.
- Don't swallow and silently log all other exception types when failing
  to use a key for signing. We want to know when (yes, when, not if...)
  this code breaks again.
- Clean up duplicated RequiresApi annotations.
amberin added a commit to amberin/orgzly-android-revived that referenced this issue Feb 13, 2024
Access to the master key that is used to wrap Ed25519 keys relies on
Android Keystore, Google Tink and androidx.security.crypto. We are using
alpha versions of the latter library, and it is notoriously flaky (see
e.g. tink-crypto/tink#535).

The main issue that caused me to go over this class was that on newly
created API 30 devices, a Ed25519 key with device lock protection could
never be used, even if they had just been created (i.e. the newly
created master key was probably not corrupted).

I realized that *something* had changed so that we now need to
authenticate in order to just create the private key object. This has
not been the case in my previous tests; authentication was not required
until we tried to _use_ they key to do something (in our case, sign an
arbitrary string).

Since we need to authenticate in more situations than before, I added a
function which can be called whenever we catch a
UserNotAuthenticatedException.

Concrete changes:

- Upgrade androidx.security.crypto from 1.1.0-alpha06 (which relies on a
  newer version of Google Tink, which is hopefully more stable). Note
  that this requires compiling against SDK >= 33, but I believe that
  should be fine.
- When preparing to use a Ed25519 key for signing, use a signing
  algorithm which actually works with those keys. This has been causing
  an InvalidKeyException during each use of a Ed25519 for a long time.
- Catch all UserNotAuthenticatedExceptions and try biometric auth when
  they occur.
- Remove the mustAuthenticate attribute, as it becomes useless now that
  we rely on catching UserNotAuthenticatedExceptions.
- Make privateKeyLoadAttempts a class attribute which can be updated
  from many places.
- Don't swallow and silently log all other exception types when failing
  to use a key for signing. We want to know when (yes, when, not if...)
  this code breaks again.
- Clean up duplicated RequiresApi annotations.
@marctatham
Copy link

Feels like this will help many fellow wanderers investigating their AEADBadTagException
tink-crypto/tink-java#23

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests