flutter_secure_storage: FlutterSecureStorage.read broken on Android 9.0 Samsung only

I just got Android 9.0 pie on my phone (physical not virtual). Everything was working fine and then once I downloaded 9.0 this error occurred. My co-worker has an Android but not a Samsung and it works fine but my Samsung produces this error when trying to read from secure storage. (Writing to secure storage doesn’t seem to be an issue). Possibly locked keychain? Not sure if this fix is local or if anyone else experiences this issue.

E/flutter (31692): [ERROR:flutter/shell/common/shell.cc(184)] Dart Error: Unhandled exception: E/flutter (31692): PlatformException(error, Unsupported value: javax.crypto.BadPaddingException: error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT, null) E/flutter (31692): #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:551:7) E/flutter (31692): #1 MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:292:18) E/flutter (31692): <asynchronous suspension> E/flutter (31692): #2 FlutterSecureStorage.read (package:flutter_secure_storage/flutter_secure_storage.dart:16:24) E/flutter (31692): <asynchronous suspension> E/flutter (31692): #3 main (package:phone_app/main.dart:27:11) E/flutter (31692): #4 _startIsolate.<anonymous closure> (dart:isolate/runtime/libisolate_patch.dart:289:19) E/flutter (31692): #5 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:171:12)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 8
  • Comments: 17 (3 by maintainers)

Commits related to this issue

Most upvoted comments

Yep, I think you’re right. My sharedPrefs was weird as well. I solved this by adding

        <application
        ...
            android:allowBackup="false"
            android:fullBackupContent="false">

on AndroidManifest.xml. So it can stop restoring stuff.

If you need fullBackupContent="yes", you can disable backup of prefs used by the plugin.

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
    <exclude domain="sharedpref" path="FlutterSecureStorage"/>
</full-backup-content>

Sorry it’s unclear to me. Why would I need to set both android:allowBackup="false" and android:fullBackupContent="false" ?

According to the Android doc, the latter is used to configure which data should be include/exclude when allowBackup is enabled, right?

So, if I understand correctly, there are 2 viable solutions:

Option 1: Disable backup completely:

<application
    ...
        android:allowBackup="false">

Option 2: Keep backup enable but exclude the shared pref used by this plugin:

<application
    ...
        android:allowBackup="true" 
        android:fullBackupContent="@xml/backup_rules">

and this file res/xml/backup_rules.xml:

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
    <exclude domain="sharedpref" path="FlutterSecureStorage"/>
</full-backup-content>

Am I correct?

I had two users w/ this problem and I did both recommendations from above:

Future<String> getEmail() async {
    String rtn;
    try {
      rtn = await flutterSecureStorage.read(key: storageUserEmailAddressKey);
    } catch (e) {
      //https://github.com/mogol/flutter_secure_storage/issues/43
      flutterSecureStorage.deleteAll();
    }
    return rtn;
  }

and this:

<application
    ...
        android:allowBackup="true" 
        android:fullBackupContent="@xml/backup_rules">

and this file res/xml/backup_rules.xml:

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
    <exclude domain="sharedpref" path="FlutterSecureStorage"/>
</full-backup-content>

And the issue was resolved

<application
    ...
        android:allowBackup="false"
        android:fullBackupContent="false">

This worked for me. However, a fresh install was needed to make it work.

When choosing to exclude the shared preferences file from backup (Option 2 of https://github.com/mogol/flutter_secure_storage/issues/43#issuecomment-674412687), now there is an additional XML file which must? be defined, if the app targets Android 12 or higher: https://developer.android.com/guide/topics/data/autobackup#include-exclude-android-12

As the doc says for the Android 11 and lower way:

Important: These XML backup rules are also used for devices running Android 12 or higher unless your app targets Android 12 (API level 31) or higher. In that case, you must specify an additional set of XML backup rules to support the changes to backup restore that were introduced for devices running Android 12 or higher.

This means one more attribute for the <application> tag in the manifest and one more XML file.

In AndroidManifest.xml:

<application
    ...
        android:dataExtractionRules="@xml/data_extraction_rules">

Create res/xml/data_extraction_rules.xml:

<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
 <cloud-backup>
   <include domain="sharedpref" path="."/>
   <exclude domain="sharedpref" path="FlutterSecureStorage"/>
 </cloud-backup>
</data-extraction-rules>

Not sure if the <include> tag is needed, maybe the <exclude> is enough. I grabbed it from the linked Android docs.

Currently Flutter 3.3.9 defines targetSdkVersion as 31, so the above may be required for new apps: https://github.com/flutter/flutter/blob/3.3.9/packages/flutter_tools/gradle/flutter.gradle#L39

Of course disabling backup by setting android:allowBackup="false" is still a valid option. In that case the above shouldn’t be done.

Well… I kind of got it working trying some weird things. In the screen you can see where’s my problem, maybe its yours too. To solve I just called “.deleteAll();” (or _sotrage.deleteAll(); following the example provided by the autor). I just call it at app’s first run using a “SharedPreferences _firstRun” variable.

I think its something with the keys recovery…idk Oh, I also tryed to solve it by configuring android:allowBackup and it did nothing…

error

UPDATE I could fix my problem in two steps: 1: Add this to the manifest:

        <application
        ...
            android:allowBackup="false"
            android:fullBackupContent="false">

2: Call .deleteAll(); in my code only once to clean everything.

After this everything got working again (no need for sharedPreffs stuff I mention above and no need to keep .deleteAll(); in the code).

You can also catch the error and erase the failing value.

For my app, it seems like once a user runs into this issue, they will not get out of it simply by the addition of the manifest changes. Trying to communicate with this random app user is also not easy to try to get them to uninstall and reinstall.

My question is: Can I catch this exception in Flutter-space and handle it more gracefully?

I saw a comment somewhere among the discussions here on Github that suggested catching the exception and then running deleteAll(). Would this work? I ask because I cannot reproduce this locally. I can only debug this by releasing a new version and then watching the error reporting service for a day or two waiting for results from the handful of users that have run into this error.

Something like:

    try {
      String accessToken = await storage.read(key: accessTokenKey);
      // obviously there is more code here
    } on PlatformException catch (exception) {
      storage.deleteAll();
      // report the error as a "handled" error for confirmation
    }