Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AEADBadTagException AndroidX Jetpack Security startup crash #23

Open
ahibrahimleague opened this issue Jan 23, 2024 · 10 comments
Open

AEADBadTagException AndroidX Jetpack Security startup crash #23

ahibrahimleague opened this issue Jan 23, 2024 · 10 comments
Labels

Comments

@ahibrahimleague
Copy link

ahibrahimleague commented Jan 23, 2024

Help us help you

We're using Tink via AndroidX Jetpack Security v1.1.0-alpha06 and using the non-deprecated EncryptedSharedPreferences.create method that avoids the race condition when creating a global MasterKey.

The crash happens on: Device - Pixel 7 pro, OS - Android 14

Describe the bug:

The crash isn't consistently reproducible we have crash reports from different devices and across different Android SDK versions.
But according to this stack trace, it's happening on startup during Application.onCreate.

What was the expected behavior?

No crashes.

How can we reproduce the bug?

We're creating an EncryptedSharedPreference instance using the below snippet.

class SharedPreferencesDataSource(
    context: Context,
    filename: String
) {

    companion object {
        fun getEncryptedSharedPreferences(context: Context, filename: String) =
            EncryptedSharedPreferences.create(
                context,
                "${filename}_secure",
                getOrCreateKey(context),
                EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
                EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
            )

        private fun getOrCreateKey(context: Context) =
            MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
                .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
                .build()
    }
}

Do you have any debugging information?

I've posted a reproducer below.

What version of Tink are you using?

Jetpack Security is using Tink v 1.8.0 behind the scenes.

Can you tell us more about your development environment?
N/A

Is there anything else you'd like to add?

I've read this comment, and mentioned

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'm not sure, though, how this can be caused? We're creating EncryptedSharedPreferences instances, but we never touch the MasterKey used to encrypt the data there.

@ahibrahimleague
Copy link
Author

ahibrahimleague commented Jan 23, 2024

Fortunately, I've managed to reproduce the issue. The issue is related to backup. Ideally it should be solved gracefully handled from within the app, without the need to allowBackup=false

Here's a reproducer project (tested against Android 13)

  1. Run the project on the device
  2. Then run ./test_d2d.sh com.example.jetpacksecurityreproducer (Note the backup script is taken from Google's guide to test device 2 device backup.
  3. Try running the app, crashe happens, you'll find the below stacktrace.

I believe a quick solution is to disallow backups; however, IMO, it's not feasible to ask every app that uses Tink/Jetpack security to disallow backups and make the user lose a useful feature such as backups.

FATAL EXCEPTION: main
Process: com.example.jetpacksecurityreproducer, PID: 15116
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.jetpacksecurityreproducer/com.example.jetpacksecurityreproducer.MainActivity}: javax.crypto.AEADBadTagException
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3646)
	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3783)
	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101)
	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:138)
	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2308)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loopOnce(Looper.java:201)
	at android.os.Looper.loop(Looper.java:288)
	at android.app.ActivityThread.main(ActivityThread.java:7966)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:942)
Caused by: javax.crypto.AEADBadTagException
	at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:611)
	at javax.crypto.Cipher.doFinal(Cipher.java:2114)
	at com.google.crypto.tink.integration.android.AndroidKeystoreAesGcm.decryptInternal(AndroidKeystoreAesGcm.java:118)
	at com.google.crypto.tink.integration.android.AndroidKeystoreAesGcm.decrypt(AndroidKeystoreAesGcm.java:101)
	at com.google.crypto.tink.KeysetHandle.decrypt(KeysetHandle.java:919)
	at com.google.crypto.tink.KeysetHandle.readWithAssociatedData(KeysetHandle.java:804)
	at com.google.crypto.tink.KeysetHandle.read(KeysetHandle.java:785)
	at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.readMasterkeyDecryptAndParseKeyset(AndroidKeysetManager.java:381)
	at com.google.crypto.tink.integration.android.AndroidKeysetManager$Builder.build(AndroidKeysetManager.java:297)
	at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:169)
	at androidx.security.crypto.EncryptedSharedPreferences.create(EncryptedSharedPreferences.java:130)
	at com.example.jetpacksecurityreproducer.MainActivity$sharedPreferences$2.invoke(MainActivity.kt:19)
	at com.example.jetpacksecurityreproducer.MainActivity$sharedPreferences$2.invoke(MainActivity.kt:18)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at com.example.jetpacksecurityreproducer.MainActivity.getSharedPreferences(MainActivity.kt:18)
	at com.example.jetpacksecurityreproducer.MainActivity.onCreate(MainActivity.kt:31)
	at android.app.Activity.performCreate(Activity.java:8348)
	at android.app.Activity.performCreate(Activity.java:8327)
	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1427)
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3627)
	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3783) 
	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101) 
	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:138) 
	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2308) 
	at android.os.Handler.dispatchMessage(Handler.java:106) 
	at android.os.Looper.loopOnce(Looper.java:201) 
	at android.os.Looper.loop(Looper.java:288) 
	at android.app.ActivityThread.main(ActivityThread.java:7966) 
	at java.lang.reflect.Method.invoke(Native Method) 
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) 
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:942) 
Caused by: android.security.KeyStoreException: Signature/MAC verification failed (internal Keystore code: -30 message: In KeystoreOperation::finish

Caused by:
    0: In finish: KeyMint::finish failed.
    1: Error::Km(ErrorCode(-30))) (public error code: 10 internal Keystore code: -30)
	at android.security.KeyStore2.getKeyStoreException(KeyStore2.java:369)
	at android.security.KeyStoreOperation.handleExceptions(KeyStoreOperation.java:78)
	at android.security.KeyStoreOperation.finish(KeyStoreOperation.java:128)
	at android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer$MainDataStream.finish(KeyStoreCryptoOperationChunkedStreamer.java:228)
	at android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:181)
	at android.security.keystore2.AndroidKeyStoreAuthenticatedAESCipherSpi$BufferAllOutputUntilDoFinalStreamer.doFinal(AndroidKeyStoreAuthenticatedAESCipherSpi.java:396)
	at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:603)
	... 31 more

JetpackSecurityReproducer.zip

@juergw
Copy link
Contributor

juergw commented Jan 24, 2024

Thanks for the detailed report. Others have had the same issue before, that's why a warning was added to not back up these files:
https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:security/security-crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java;l=54;bpv=1
Instead of disabling backup, you can also just exclude the encrypted files, see https://developer.android.com/guide/topics/data/autobackup#IncludingFiles

There is nothing else we can do, unfortunately. The keys stored in keystore are not backed up. So if you restore the backup, you don't have the key anymore, and therefore you can't decrypt anymore.

Also, note that Android will encrypt the data by default, see https://source.android.com/docs/security/features/encryption/file-based.

@ahibrahimleague
Copy link
Author

ahibrahimleague commented Jan 24, 2024

Thanks @juergw for the response.

Does that mean a solution would be, to handle this without declaring backup rules, during EncryptedSharedPreferences.create, would be to catch GeneralSecurityException and delete the existing keys from the KeyStore, and delete the shared prefs files themselves and recreate them? (I know this would make existing data to be deleted, but I don't believe it will matter that much when an app is being reinstalled)

Also, note that Android will encrypt the data by default,

If this was the case, Do you think storing the keysets into plain text format rather than on Android's KeyStore would be more reliable during backup/restore? (Not sure about the Security implications of this though)

@ahibrahimleague
Copy link
Author

ahibrahimleague commented Jan 24, 2024

I'd also like to add that it turned out that even with allowBackup="false" the same crash still happens on some devices (I couldn't figure out how to reproduce this though), so it looks like it can happen even outside the realm of Auto backup.

@juergw
Copy link
Contributor

juergw commented Jan 31, 2024

It would be enough to delete the shared preferences, there is no need to delete the (newly created) key in keystore.

@dpasirst
Copy link

dpasirst commented Feb 4, 2024

I'm experiencing this issue (repeatable) on a single device but not others: Pixel 7 running Android 14 (up to date with latest official updates). I'm using the same versions of jetpack as mentioned. I have tried: disabling backups via the manifest and I have tried excluding the encrypted files. Neither work, it does not appear to be the cause of the problem.
Is it possible a past backup would cause the issue even after the manifest and/or backup rules were updated?

I have also factory reset the device and the error still occurs.

in my case, a clean install of the app on that device, launches and works. if I kill the app and restart (all future restarts) fail with the error from this report. In my case, deleting the prefs is not a working solution.

I have also discovered that once EncryptedSharedPreferences.create shows the error so does EncryptedFile.Builder.

@dpasirst
Copy link

dpasirst commented Feb 5, 2024

As a follow-up, I was finally able to get the app to no longer have this failure. I used Device Explorer in Android Studio to manually delete the offending shared prefs file and let the app recreate it.

@rynkowsg
Copy link

rynkowsg commented Mar 6, 2024

This solution that targets the scenario when the encrypted file is backed up and system restored it, but lost the key used for encryption (e.g. app was uninstalled and installed again).

Here is the gist if someone still needed it:
https://gist.github.com/rynkowsg/86ebd680a67669dfcece4cc9ec9974df
The approach there relies on recreating the shared preferences when they can not be read.

Tested with "androidx.security:security-crypto:1.1.0-alpha06".

In addition, it is good to explicitly set which files should be backed up, with:

        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        tools:targetApi="upside_down_cake"

android:dataExtractionRules for API >= 31
android:fullBackupContent for API < 31
tools:targetApi to fix warning, set to your compile sdk version

@juergw
Copy link
Contributor

juergw commented Oct 3, 2024

I think the main problem here is that both jetpack and Tink tried to give a too simple API to the user. That API works fine in most cases, but when something goes wrong, it is much more difficult for the user of the API to figure out what is happening. So we have decided to provide a more low-level API that forces the user to write more code themselves. But it makes it easier to understand what is going on, so I think it will be easier to maintain.

Here is the new API:
https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/integration/android/AndroidKeystore.java

and here is how a user might use this API in their API:
https://github.com/tink-crypto/tink-java/blob/main/examples/android/helloworld/app/src/main/java/com/helloworld/TinkApplication.java

@alvindizon
Copy link

I think the main problem here is that both jetpack and Tink tried to give a too simple API to the user. That API works fine in most cases, but when something goes wrong, it is much more difficult for the user of the API to figure out what is happening. So we have decided to provide a more low-level API that forces the user to write more code themselves. But it makes it easier to understand what is going on, so I think it will be easier to maintain.

Here is the new API: https://github.com/tink-crypto/tink-java/blob/main/src/main/java/com/google/crypto/tink/integration/android/AndroidKeystore.java

and here is how a user might use this API in their API: https://github.com/tink-crypto/tink-java/blob/main/examples/android/helloworld/app/src/main/java/com/helloworld/TinkApplication.java

Thank you for this, when will this be released?

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

No branches or pull requests

5 participants