From 989cd3c45fb84e06740cafd0b38f0467e5313cb7 Mon Sep 17 00:00:00 2001 From: Igor Kharakhordin Date: Wed, 14 Jan 2026 10:01:35 +0100 Subject: [PATCH 1/6] [Android] Enable StrongBox by default with a fallback --- flutter_secure_storage/CHANGELOG.md | 5 +++ .../FlutterSecureStorage.java | 1 + .../FlutterSecureStoragePlugin.java | 8 ++++ .../ciphers/KeyCipherImplementationRSA18.java | 41 ++++++++++++++----- .../KeyCipherImplementationRSAOAEP.java | 5 ++- .../lib/flutter_secure_storage.dart | 13 ++++++ .../test_flutter_secure_storage_platform.dart | 6 +++ flutter_secure_storage/pubspec.yaml | 32 ++++++++++++--- flutter_secure_storage_linux/pubspec.yaml | 6 ++- ...ter_secure_storage_platform_interface.dart | 13 ++++++ ...method_channel_flutter_secure_storage.dart | 16 ++++++++ flutter_secure_storage_web/pubspec.yaml | 6 ++- .../flutter_secure_storage_windows_ffi.dart | 9 ++++ .../flutter_secure_storage_windows_stub.dart | 9 ++++ flutter_secure_storage_windows/pubspec.yaml | 6 ++- 15 files changed, 155 insertions(+), 21 deletions(-) diff --git a/flutter_secure_storage/CHANGELOG.md b/flutter_secure_storage/CHANGELOG.md index 45a58b1d..ab5b07d5 100644 --- a/flutter_secure_storage/CHANGELOG.md +++ b/flutter_secure_storage/CHANGELOG.md @@ -1,3 +1,8 @@ +## Fork + +* Enabled StrongBox by default, use fallback if it's not available. +* [Android] Method to check if an Android device supports Strongbox + ## 10.0.0 This major release brings significant security improvements, platform updates, and modernization across all supported platforms. diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStorage.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStorage.java index 4ba36c34..c72b8b7f 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStorage.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStorage.java @@ -1145,6 +1145,7 @@ private SharedPreferences initializeEncryptedSharedPreferencesManager(Context co .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setKeySize(256).build()) + .setRequestStrongBoxBacked(true) .build(); return EncryptedSharedPreferences.create( context, diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java index 427f9137..8d6254ec 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java @@ -1,6 +1,7 @@ package com.it_nomads.fluttersecurestorage; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -26,10 +27,12 @@ public class FlutterSecureStoragePlugin implements MethodCallHandler, FlutterPlu private FlutterSecureStorage secureStorage; private HandlerThread workerThread; private Handler workerThreadHandler; + private boolean isStrongBoxAvailable; public void initInstance(BinaryMessenger messenger, Context context) { try { secureStorage = new FlutterSecureStorage(context); + isStrongBoxAvailable = context.getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE); workerThread = new HandlerThread("com.it_nomads.fluttersecurestorage.worker"); workerThread.start(); @@ -124,6 +127,11 @@ public void run() { Map options = (Map) ((Map) call.arguments).get("options"); FlutterSecureStorageConfig config = new FlutterSecureStorageConfig(options); + if (call.method.equals("isStrongBoxSupported")) { + result.success(isStrongBoxAvailable); + return; + } + secureStorage.initialize(config, new SecurePreferencesCallback<>() { @Override public void onSuccess(Void unused) { diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSA18.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSA18.java index 73dc55fd..62a3a8fc 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSA18.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSA18.java @@ -5,6 +5,9 @@ import android.os.Build; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; +import android.security.keystore.StrongBoxUnavailableException; + +import androidx.annotation.RequiresApi; import com.it_nomads.fluttersecurestorage.FlutterSecureStorageConfig; @@ -137,26 +140,38 @@ private void setLocale(Locale locale) { context.createConfigurationContext(config); } + private AlgorithmParameterSpec getSpec(boolean isStrongBoxBacked) { + Calendar start = Calendar.getInstance(); + Calendar end = Calendar.getInstance(); + end.add(Calendar.YEAR, 25); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return makeAlgorithmParameterSpecLegacy(context, start, end); + } + + return makeAlgorithmParameterSpec(context, start, end, isStrongBoxBacked); + } + + @RequiresApi(api = Build.VERSION_CODES.P) private void createKeys(Context context) throws Exception { final Locale localeBeforeFakingEnglishLocale = Locale.getDefault(); try { setLocale(Locale.ENGLISH); - Calendar start = Calendar.getInstance(); - Calendar end = Calendar.getInstance(); - end.add(Calendar.YEAR, 25); KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(TYPE_RSA, KEYSTORE_PROVIDER_ANDROID); AlgorithmParameterSpec spec; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - spec = makeAlgorithmParameterSpecLegacy(context, start, end); - } else { - spec = makeAlgorithmParameterSpec(context, start, end); - } + try { + spec = getSpec(true); + + kpGenerator.initialize(spec); + kpGenerator.generateKeyPair(); + } catch (StrongBoxUnavailableException e) { + spec = getSpec(false); - kpGenerator.initialize(spec); - kpGenerator.generateKeyPair(); + kpGenerator.initialize(spec); + kpGenerator.generateKeyPair(); + } } finally { setLocale(localeBeforeFakingEnglishLocale); } @@ -174,7 +189,8 @@ private AlgorithmParameterSpec makeAlgorithmParameterSpecLegacy(Context context, .build(); } - protected AlgorithmParameterSpec makeAlgorithmParameterSpec(Context context, Calendar start, Calendar end) { + @RequiresApi(api = Build.VERSION_CODES.M) + protected AlgorithmParameterSpec makeAlgorithmParameterSpec(Context context, Calendar start, Calendar end, boolean isStrongBoxBacked) { final KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT) .setCertificateSubject(new X500Principal("CN=" + keyAlias)) .setDigests(KeyProperties.DIGEST_SHA256) @@ -183,6 +199,9 @@ protected AlgorithmParameterSpec makeAlgorithmParameterSpec(Context context, Cal .setCertificateSerialNumber(BigInteger.valueOf(1)) .setCertificateNotBefore(start.getTime()) .setCertificateNotAfter(end.getTime()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && isStrongBoxBacked) { + builder.setIsStrongBoxBacked(true); + } return builder.build(); } } diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSAOAEP.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSAOAEP.java index d38e08e9..51d701da 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSAOAEP.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSAOAEP.java @@ -32,7 +32,7 @@ protected String createKeyAlias() { @RequiresApi(api = Build.VERSION_CODES.M) @Override - protected AlgorithmParameterSpec makeAlgorithmParameterSpec(Context context, Calendar start, Calendar end) { + protected AlgorithmParameterSpec makeAlgorithmParameterSpec(Context context, Calendar start, Calendar end, boolean isStrongBoxBacked) { final KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT) .setCertificateSubject(new X500Principal("CN=" + keyAlias)) .setDigests(KeyProperties.DIGEST_SHA256) @@ -41,6 +41,9 @@ protected AlgorithmParameterSpec makeAlgorithmParameterSpec(Context context, Cal .setCertificateSerialNumber(BigInteger.valueOf(1)) .setCertificateNotBefore(start.getTime()) .setCertificateNotAfter(end.getTime()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && isStrongBoxBacked) { + builder.setIsStrongBoxBacked(true); + } return builder.build(); } diff --git a/flutter_secure_storage/lib/flutter_secure_storage.dart b/flutter_secure_storage/lib/flutter_secure_storage.dart index 92291cd5..e1cdf5f0 100644 --- a/flutter_secure_storage/lib/flutter_secure_storage.dart +++ b/flutter_secure_storage/lib/flutter_secure_storage.dart @@ -344,6 +344,19 @@ class FlutterSecureStorage { }); } + /// [aOptions] optional Android options + Future isStrongBoxSupported({ + AndroidOptions? aOptions, + }) async { + if (defaultTargetPlatform == TargetPlatform.android) { + return _platform.isStrongBoxSupported( + options: aOptions?.params ?? this.aOptions.params, + ); + } else { + throw UnsupportedError(_unsupportedPlatform); + } + } + /// Select correct options based on current platform Map _selectOptions( AppleOptions? iOptions, diff --git a/flutter_secure_storage/lib/test/test_flutter_secure_storage_platform.dart b/flutter_secure_storage/lib/test/test_flutter_secure_storage_platform.dart index 74a4f7ae..0b63d2bc 100644 --- a/flutter_secure_storage/lib/test/test_flutter_secure_storage_platform.dart +++ b/flutter_secure_storage/lib/test/test_flutter_secure_storage_platform.dart @@ -52,4 +52,10 @@ class TestFlutterSecureStoragePlatform extends FlutterSecureStoragePlatform { required Map options, }) async => data[key] = value; + + @override + Future isStrongBoxSupported({ + required Map options, + }) async => + true; } diff --git a/flutter_secure_storage/pubspec.yaml b/flutter_secure_storage/pubspec.yaml index 708ec676..26b631db 100644 --- a/flutter_secure_storage/pubspec.yaml +++ b/flutter_secure_storage/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_secure_storage description: A Flutter plugin for securely storing sensitive data using encrypted storage. -version: 10.0.0 +version: 10.0.0-fork repository: https://github.com/mogol/flutter_secure_storage/tree/develop/flutter_secure_storage environment: @@ -31,11 +31,31 @@ dependencies: # implementation constraints as "any". We cannot do it right now as it fails pub publish # validation, so we set a ^ constraint. # https://github.com/flutter/flutter/issues/46264 - flutter_secure_storage_darwin: ^0.2.0 - flutter_secure_storage_linux: ^3.0.0 - flutter_secure_storage_platform_interface: ^2.0.1 - flutter_secure_storage_web: ^2.1.0 - flutter_secure_storage_windows: ^4.1.0 + flutter_secure_storage_darwin: + git: + url: git@github.com:QuickBirdEng/flutter_secure_storage.git + path: flutter_secure_storage_darwin + ref: v10.0.0-fork + flutter_secure_storage_linux: + git: + url: git@github.com:QuickBirdEng/flutter_secure_storage.git + path: flutter_secure_storage_linux + ref: v10.0.0-fork + flutter_secure_storage_platform_interface: + git: + url: git@github.com:QuickBirdEng/flutter_secure_storage.git + path: flutter_secure_storage_platform_interface + ref: v10.0.0-fork + flutter_secure_storage_web: + git: + url: git@github.com:QuickBirdEng/flutter_secure_storage.git + path: flutter_secure_storage_web + ref: v10.0.0-fork + flutter_secure_storage_windows: + git: + url: git@github.com:QuickBirdEng/flutter_secure_storage.git + path: flutter_secure_storage_windows + ref: v10.0.0-fork meta: ^1.3.0 dev_dependencies: diff --git a/flutter_secure_storage_linux/pubspec.yaml b/flutter_secure_storage_linux/pubspec.yaml index a3884f84..b64c79aa 100644 --- a/flutter_secure_storage_linux/pubspec.yaml +++ b/flutter_secure_storage_linux/pubspec.yaml @@ -10,7 +10,11 @@ environment: dependencies: flutter: sdk: flutter - flutter_secure_storage_platform_interface: ^2.0.0 + flutter_secure_storage_platform_interface: + git: + url: git@github.com:QuickBirdEng/flutter_secure_storage.git + path: flutter_secure_storage_platform_interface + ref: v10.0.0-fork flutter: plugin: diff --git a/flutter_secure_storage_platform_interface/lib/flutter_secure_storage_platform_interface.dart b/flutter_secure_storage_platform_interface/lib/flutter_secure_storage_platform_interface.dart index 89575675..bb38c3b4 100644 --- a/flutter_secure_storage_platform_interface/lib/flutter_secure_storage_platform_interface.dart +++ b/flutter_secure_storage_platform_interface/lib/flutter_secure_storage_platform_interface.dart @@ -120,4 +120,17 @@ abstract class FlutterSecureStoragePlatform extends PlatformInterface { Future deleteAll({ required Map options, }); + + /// Checks if the android device supports secure hardware-backed storage. + /// + /// Returns: + /// - A [Future] that resolves to `true` if the device supports secure + /// hardware-backed storage, or `false` otherwise. + Future isStrongBoxSupported({ + required Map options, + }) { + throw UnsupportedError( + 'isStrongBoxSupported() is not available on this platform', + ); + } } diff --git a/flutter_secure_storage_platform_interface/lib/src/method_channel_flutter_secure_storage.dart b/flutter_secure_storage_platform_interface/lib/src/method_channel_flutter_secure_storage.dart index 1cf5365b..3c864f67 100644 --- a/flutter_secure_storage_platform_interface/lib/src/method_channel_flutter_secure_storage.dart +++ b/flutter_secure_storage_platform_interface/lib/src/method_channel_flutter_secure_storage.dart @@ -114,4 +114,20 @@ class MethodChannelFlutterSecureStorage extends FlutterSecureStoragePlatform { 'value': value, 'options': options, }); + + @override + Future isStrongBoxSupported({ + required Map options, + }) async { + if (defaultTargetPlatform != TargetPlatform.android) { + throw UnsupportedError('StrongBox is only supported on Android.'); + } + return (await _channel.invokeMethod( + 'isStrongBoxSupported', + { + 'options': options, + }, + )) ?? + false; + } } diff --git a/flutter_secure_storage_web/pubspec.yaml b/flutter_secure_storage_web/pubspec.yaml index 1907feb1..f6fc7400 100644 --- a/flutter_secure_storage_web/pubspec.yaml +++ b/flutter_secure_storage_web/pubspec.yaml @@ -10,7 +10,11 @@ environment: dependencies: flutter: sdk: flutter - flutter_secure_storage_platform_interface: ^2.0.0 + flutter_secure_storage_platform_interface: + git: + url: git@github.com:QuickBirdEng/flutter_secure_storage.git + path: flutter_secure_storage_platform_interface + ref: v10.0.0-fork flutter_web_plugins: sdk: flutter web: ">=0.5.0 <2.0.0" diff --git a/flutter_secure_storage_windows/lib/src/flutter_secure_storage_windows_ffi.dart b/flutter_secure_storage_windows/lib/src/flutter_secure_storage_windows_ffi.dart index 115326c4..f4a42269 100644 --- a/flutter_secure_storage_windows/lib/src/flutter_secure_storage_windows_ffi.dart +++ b/flutter_secure_storage_windows/lib/src/flutter_secure_storage_windows_ffi.dart @@ -178,6 +178,15 @@ class FlutterSecureStorageWindows extends FlutterSecureStoragePlatform { await _backwardCompatible.delete(key: key, options: options); } } + + @override + Future isStrongBoxSupported({ + required Map options, + }) async { + throw UnsupportedError( + 'isStrongBoxSupported() is not available on this platform', + ); + } } /// Creates a custom instance of `FlutterSecureStorageWindows` for testing. diff --git a/flutter_secure_storage_windows/lib/src/flutter_secure_storage_windows_stub.dart b/flutter_secure_storage_windows/lib/src/flutter_secure_storage_windows_stub.dart index b6647f0a..becb68c6 100644 --- a/flutter_secure_storage_windows/lib/src/flutter_secure_storage_windows_stub.dart +++ b/flutter_secure_storage_windows/lib/src/flutter_secure_storage_windows_stub.dart @@ -49,6 +49,15 @@ class FlutterSecureStorageWindows extends FlutterSecureStoragePlatform { }) => Future.value(); + @override + Future isStrongBoxSupported({ + required Map options, + }) async { + throw UnsupportedError( + 'isStrongBoxSupported() is not available on this platform', + ); + } + // @override // Future isCupertinoProtectedDataAvailable() => Future.value(true); // diff --git a/flutter_secure_storage_windows/pubspec.yaml b/flutter_secure_storage_windows/pubspec.yaml index 15c63ab4..cf3e4c58 100644 --- a/flutter_secure_storage_windows/pubspec.yaml +++ b/flutter_secure_storage_windows/pubspec.yaml @@ -11,7 +11,11 @@ dependencies: ffi: ^2.0.0 flutter: sdk: flutter - flutter_secure_storage_platform_interface: ^2.0.0 + flutter_secure_storage_platform_interface: + git: + url: git@github.com:QuickBirdEng/flutter_secure_storage.git + path: flutter_secure_storage_platform_interface + ref: v10.0.0-fork path: ^1.8.0 path_provider: ^2.0.0 win32: ^5.5.4 From 6c7943d379d962c6fa51c17e198ea56640d2b440 Mon Sep 17 00:00:00 2001 From: Igor Kharakhordin Date: Wed, 14 Jan 2026 10:42:48 +0100 Subject: [PATCH 2/6] [Android] Use old algorithms as default --- flutter_secure_storage/CHANGELOG.md | 3 ++- .../lib/options/android_options.dart | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/flutter_secure_storage/CHANGELOG.md b/flutter_secure_storage/CHANGELOG.md index ab5b07d5..a5049640 100644 --- a/flutter_secure_storage/CHANGELOG.md +++ b/flutter_secure_storage/CHANGELOG.md @@ -1,7 +1,8 @@ ## Fork -* Enabled StrongBox by default, use fallback if it's not available. +* [Android] Enabled StrongBox by default, use fallback if it's not available. * [Android] Method to check if an Android device supports Strongbox +* [Android] Use old algorithms as default (migration to AES_GCM_NoPadding is broken and fails) ## 10.0.0 This major release brings significant security improvements, platform updates, and modernization across all supported platforms. diff --git a/flutter_secure_storage/lib/options/android_options.dart b/flutter_secure_storage/lib/options/android_options.dart index 59e20f1f..34fb44d6 100644 --- a/flutter_secure_storage/lib/options/android_options.dart +++ b/flutter_secure_storage/lib/options/android_options.dart @@ -48,13 +48,13 @@ class AndroidOptions extends Options { 'Your data will be automatically migrated to custom ciphers on first ' 'access. Remove this parameter - it will be ignored.') bool encryptedSharedPreferences = false, - bool resetOnError = true, + bool resetOnError = false, bool migrateOnAlgorithmChange = true, bool enforceBiometrics = false, KeyCipherAlgorithm keyCipherAlgorithm = - KeyCipherAlgorithm.RSA_ECB_OAEPwithSHA_256andMGF1Padding, + KeyCipherAlgorithm.RSA_ECB_PKCS1Padding, StorageCipherAlgorithm storageCipherAlgorithm = - StorageCipherAlgorithm.AES_GCM_NoPadding, + StorageCipherAlgorithm.AES_CBC_PKCS7Padding, this.sharedPreferencesName, this.preferencesKeyPrefix, this.biometricPromptTitle, @@ -80,7 +80,7 @@ class AndroidOptions extends Options { 'The Jetpack Security library is deprecated by Google. ' 'Remove this parameter - it will be ignored.') bool encryptedSharedPreferences = false, - bool resetOnError = true, + bool resetOnError = false, bool migrateOnAlgorithmChange = true, bool enforceBiometrics = false, this.sharedPreferencesName, @@ -91,8 +91,8 @@ class AndroidOptions extends Options { _resetOnError = resetOnError, _migrateOnAlgorithmChange = migrateOnAlgorithmChange, _enforceBiometrics = enforceBiometrics, - _keyCipherAlgorithm = KeyCipherAlgorithm.AES_GCM_NoPadding, - _storageCipherAlgorithm = StorageCipherAlgorithm.AES_GCM_NoPadding; + _keyCipherAlgorithm = KeyCipherAlgorithm.RSA_ECB_PKCS1Padding, + _storageCipherAlgorithm = StorageCipherAlgorithm.AES_CBC_PKCS7Padding; /// EncryptedSharedPrefences are only available on API 23 and greater final bool _encryptedSharedPreferences; From 2ccf8d029d9e11bddf58aaa55fe20442e614de5a Mon Sep 17 00:00:00 2001 From: Igor Kharakhordin Date: Tue, 23 Dec 2025 22:57:49 +0100 Subject: [PATCH 3/6] [Android] Create separate instances of FlutterSecureStorage --- flutter_secure_storage/CHANGELOG.md | 2 + .../FlutterSecureStorage.java | 4 +- .../FlutterSecureStoragePlugin.java | 42 +++++++++++-------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/flutter_secure_storage/CHANGELOG.md b/flutter_secure_storage/CHANGELOG.md index a5049640..0a28bb6b 100644 --- a/flutter_secure_storage/CHANGELOG.md +++ b/flutter_secure_storage/CHANGELOG.md @@ -3,6 +3,8 @@ * [Android] Enabled StrongBox by default, use fallback if it's not available. * [Android] Method to check if an Android device supports Strongbox * [Android] Use old algorithms as default (migration to AES_GCM_NoPadding is broken and fails) +* [Android] Create separate instances of FlutterSecureStorage with different configs/options +* [iOS] Add option to use secure enclave (based on [#989 PR](https://github.com/juliansteenbakker/flutter_secure_storage/pull/989)) ## 10.0.0 This major release brings significant security improvements, platform updates, and modernization across all supported platforms. diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStorage.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStorage.java index c72b8b7f..e67ffc32 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStorage.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStorage.java @@ -35,7 +35,7 @@ public class FlutterSecureStorage { private static final String TAG = "FlutterSecureStorage"; private static final Charset charset = StandardCharsets.UTF_8; - private static final String SHARED_PREFERENCES_CONFIG_NAME = "FlutterSecureStorageConfiguration"; + private static final String SHARED_PREFERENCES_CONFIG_NAME_SUFFIX = "Configuration"; private FlutterSecureStorageConfig config; @NonNull @@ -158,7 +158,7 @@ protected void initialize(FlutterSecureStorageConfig config, SecurePreferencesCa ); SharedPreferences configSource = context.getSharedPreferences( - SHARED_PREFERENCES_CONFIG_NAME, + config.getSharedPreferencesName() + SHARED_PREFERENCES_CONFIG_NAME_SUFFIX, Context.MODE_PRIVATE ); diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java index 8d6254ec..92083335 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java @@ -24,30 +24,32 @@ public class FlutterSecureStoragePlugin implements MethodCallHandler, FlutterPlu private static final String TAG = "FlutterSecureStoragePlugin"; private MethodChannel channel; - private FlutterSecureStorage secureStorage; private HandlerThread workerThread; private Handler workerThreadHandler; private boolean isStrongBoxAvailable; + private FlutterPluginBinding binding; - public void initInstance(BinaryMessenger messenger, Context context) { + public FlutterSecureStorage initInstance(Context context) { try { - secureStorage = new FlutterSecureStorage(context); - isStrongBoxAvailable = context.getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE); - - workerThread = new HandlerThread("com.it_nomads.fluttersecurestorage.worker"); - workerThread.start(); - workerThreadHandler = new Handler(workerThread.getLooper()); - - channel = new MethodChannel(messenger, "plugins.it_nomads.com/flutter_secure_storage"); - channel.setMethodCallHandler(this); + return new FlutterSecureStorage(context); } catch (Exception e) { Log.e(TAG, "Registration failed", e); + return null; } } @Override public void onAttachedToEngine(FlutterPluginBinding binding) { - initInstance(binding.getBinaryMessenger(), binding.getApplicationContext()); + this.binding = binding; + + isStrongBoxAvailable = binding.getApplicationContext().getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE); + + workerThread = new HandlerThread("com.it_nomads.fluttersecurestorage.worker"); + workerThread.start(); + workerThreadHandler = new Handler(workerThread.getLooper()); + + channel = new MethodChannel(binding.getBinaryMessenger(), "plugins.it_nomads.com/flutter_secure_storage"); + channel.setMethodCallHandler(this); } @Override @@ -59,7 +61,6 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { channel.setMethodCallHandler(null); channel = null; } - secureStorage = null; } @Override @@ -70,7 +71,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result rawResult) { } @SuppressWarnings("unchecked") - private String getKeyFromCall(MethodCall call) { + private String getKeyFromCall(MethodCall call, FlutterSecureStorage secureStorage) { Map arguments = (Map) call.arguments; return secureStorage.addPrefixToKey((String) arguments.get("key")); } @@ -129,6 +130,11 @@ public void run() { if (call.method.equals("isStrongBoxSupported")) { result.success(isStrongBoxAvailable); + } + + FlutterSecureStorage secureStorage = initInstance(binding.getApplicationContext()); + if (secureStorage == null) { + result.error("Could not initialize FlutterSecureStorage", null, null); return; } @@ -138,7 +144,7 @@ public void onSuccess(Void unused) { try { switch (call.method) { case "write": { - String key = getKeyFromCall(call); + String key = getKeyFromCall(call, secureStorage); String value = getValueFromCall(call); if (value != null) { @@ -150,7 +156,7 @@ public void onSuccess(Void unused) { break; } case "read": { - String key = getKeyFromCall(call); + String key = getKeyFromCall(call, secureStorage); if (secureStorage.containsKey(key)) { String value = secureStorage.read(key); @@ -165,14 +171,14 @@ public void onSuccess(Void unused) { break; } case "containsKey": { - String key = getKeyFromCall(call); + String key = getKeyFromCall(call, secureStorage); boolean containsKey = secureStorage.containsKey(key); result.success(containsKey); break; } case "delete": { - String key = getKeyFromCall(call); + String key = getKeyFromCall(call, secureStorage); secureStorage.delete(key); result.success(null); From b0188d24f878dade6eed10683b953dfec627b1be Mon Sep 17 00:00:00 2001 From: Igor Kharakhordin Date: Wed, 14 Jan 2026 09:46:00 +0100 Subject: [PATCH 4/6] [Android] Use separate keys for different storage instances --- flutter_secure_storage/CHANGELOG.md | 1 + .../ciphers/KeyCipherImplementationAES23.java | 33 ++++++++++++++----- .../ciphers/KeyCipherImplementationRSA18.java | 8 ++++- .../KeyCipherImplementationRSAOAEP.java | 8 ++++- .../ciphers/StorageCipherFactory.java | 6 ++-- .../ciphers/StorageCipherFunction.java | 4 ++- .../StorageCipherImplementationAES18.java | 28 +++++++++++----- .../StorageCipherImplementationAES23.java | 29 +++++++++++----- .../StorageCipherImplementationGCM.java | 28 +++++++++++----- 9 files changed, 107 insertions(+), 38 deletions(-) diff --git a/flutter_secure_storage/CHANGELOG.md b/flutter_secure_storage/CHANGELOG.md index 0a28bb6b..717bf22b 100644 --- a/flutter_secure_storage/CHANGELOG.md +++ b/flutter_secure_storage/CHANGELOG.md @@ -4,6 +4,7 @@ * [Android] Method to check if an Android device supports Strongbox * [Android] Use old algorithms as default (migration to AES_GCM_NoPadding is broken and fails) * [Android] Create separate instances of FlutterSecureStorage with different configs/options +* [Android] Use separate keys for different storage instances * [iOS] Add option to use secure enclave (based on [#989 PR](https://github.com/juliansteenbakker/flutter_secure_storage/pull/989)) ## 10.0.0 diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationAES23.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationAES23.java index 8665de05..e2d22726 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationAES23.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationAES23.java @@ -29,11 +29,11 @@ class KeyCipherImplementationAES23 implements KeyCipher { private static final String TAG = "AESCipher23"; private static final String KEYSTORE_PROVIDER_ANDROID = "AndroidKeyStore"; - private static final String SHARED_PREFERENCES_NAME = "FlutterSecureKeyStorage"; - private static final String SHARED_PREFERENCES_KEY = "KeyStoreIV1"; private static final int IV_SIZE = 16; private static final int KEY_SIZE = 256; protected final String keyAlias; + protected final String ivStorageKey; + protected final String ivStoragePrefsName; protected final Context context; protected final FlutterSecureStorageConfig config; @@ -42,6 +42,17 @@ public KeyCipherImplementationAES23(Context context, FlutterSecureStorageConfig this.context = context; this.config = config; keyAlias = createKeyAlias(context); + + // Backward compatibility: use original storage names for default config + if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) { + ivStoragePrefsName = "FlutterSecureKeyStorage"; + ivStorageKey = "KeyStoreIV1"; + } else { + String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix(); + ivStoragePrefsName = "FlutterSecureKeyStorage_" + configId; + ivStorageKey = "KeyStoreIV1_" + configId; + } + KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID); ks.load(null); Key privateKey = ks.getKey(keyAlias, null); @@ -61,7 +72,13 @@ public Key unwrap(byte[] wrappedKey, String algorithm) throws UnsupportedOperati } protected String createKeyAlias(Context context) { - return context.getPackageName() + ".FlutterSecureStoragePluginKey"; + // Backward compatibility: use original key name for default config + if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) { + return context.getPackageName() + ".FlutterSecureStoragePluginKey"; + } + + String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix(); + return context.getPackageName() + ".FlutterSecureStoragePluginKey_" + configId; } @Override @@ -70,8 +87,8 @@ public void deleteKey() throws Exception { ks.load(null); ks.deleteEntry(keyAlias); - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - preferences.edit().remove(SHARED_PREFERENCES_KEY).apply(); + SharedPreferences preferences = context.getSharedPreferences(ivStoragePrefsName, Context.MODE_PRIVATE); + preferences.edit().remove(ivStorageKey).apply(); } @Override @@ -90,8 +107,8 @@ public Cipher getCipher(Context context) throws Exception { public Cipher getEncryptionCipher(Context context, Key key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - String ivBase64 = preferences.getString(SHARED_PREFERENCES_KEY, null); + SharedPreferences preferences = context.getSharedPreferences(ivStoragePrefsName, Context.MODE_PRIVATE); + String ivBase64 = preferences.getString(ivStorageKey, null); if (ivBase64 != null) { byte[] iv = Base64.decode(ivBase64, Base64.DEFAULT); @@ -103,7 +120,7 @@ public Cipher getEncryptionCipher(Context context, Key key) throws NoSuchPadding byte[] iv = cipher.getIV(); SharedPreferences.Editor editor = preferences.edit(); - editor.putString(SHARED_PREFERENCES_KEY, Base64.encodeToString(iv, Base64.DEFAULT)); + editor.putString(ivStorageKey, Base64.encodeToString(iv, Base64.DEFAULT)); editor.apply(); } diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSA18.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSA18.java index 62a3a8fc..3d6f7969 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSA18.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSA18.java @@ -41,7 +41,13 @@ public KeyCipherImplementationRSA18(Context context, FlutterSecureStorageConfig } protected String createKeyAlias() { - return context.getPackageName() + ".FlutterSecureStoragePluginKey"; + // Backward compatibility: use original key name for default config + if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) { + return context.getPackageName() + ".FlutterSecureStoragePluginKey"; + } + + String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix(); + return context.getPackageName() + ".FlutterSecureStoragePluginKey_" + configId; } @Override diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSAOAEP.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSAOAEP.java index 51d701da..19a9c654 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSAOAEP.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSAOAEP.java @@ -27,7 +27,13 @@ public KeyCipherImplementationRSAOAEP(Context context, FlutterSecureStorageConfi @Override protected String createKeyAlias() { - return context.getPackageName() + ".FlutterSecureStoragePluginKeyOAEP"; + // Backward compatibility: use original key name for default config + if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) { + return context.getPackageName() + ".FlutterSecureStoragePluginKeyOAEP"; + } + + String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix(); + return context.getPackageName() + ".FlutterSecureStoragePluginKeyOAEP_" + configId; } @RequiresApi(api = Build.VERSION_CODES.M) diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherFactory.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherFactory.java index 5324a399..b61141da 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherFactory.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherFactory.java @@ -82,10 +82,10 @@ private StorageCipher createStorageCipher(Context context, KeyCipher keyCipher, if (algorithm == StorageCipherAlgorithm.AES_GCM_NoPadding) { if (isKeyStoreKeyCipher(keyCipher)) { // Use KeyStore-based implementation (biometric/PIN auth capable) - return new StorageCipherImplementationAES23(context, keyCipher, cipher); + return new StorageCipherImplementationAES23(context, keyCipher, cipher, config); } else { // Use RSA-wrapped implementation (standard secure storage) - return new StorageCipherImplementationGCM(context, keyCipher, cipher); + return new StorageCipherImplementationGCM(context, keyCipher, cipher, config); } } @@ -93,7 +93,7 @@ private StorageCipher createStorageCipher(Context context, KeyCipher keyCipher, if (algorithm.storageCipher == null) { throw new Exception("No implementation available for algorithm: " + algorithm.name()); } - return algorithm.storageCipher.apply(context, keyCipher, cipher); + return algorithm.storageCipher.apply(context, keyCipher, cipher, config); } /** diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherFunction.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherFunction.java index 4d25c5e7..c92a384c 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherFunction.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherFunction.java @@ -2,9 +2,11 @@ import android.content.Context; +import com.it_nomads.fluttersecurestorage.FlutterSecureStorageConfig; + import javax.crypto.Cipher; @FunctionalInterface interface StorageCipherFunction { - StorageCipher apply(Context context, KeyCipher keyCipher, Cipher cipher) throws Exception; + StorageCipher apply(Context context, KeyCipher keyCipher, Cipher cipher, FlutterSecureStorageConfig config) throws Exception; } diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationAES18.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationAES18.java index 2d92b458..5f986519 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationAES18.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationAES18.java @@ -4,6 +4,8 @@ import android.content.SharedPreferences; import android.util.Base64; +import com.it_nomads.fluttersecurestorage.FlutterSecureStorageConfig; + import java.security.Key; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; @@ -15,19 +17,29 @@ public class StorageCipherImplementationAES18 implements StorageCipher { private static final int keySize = 16; private static final String KEY_ALGORITHM = "AES"; - private static final String SHARED_PREFERENCES_NAME = "FlutterSecureKeyStorage"; - private static final String SHARED_PREFERENCES_KEY = "VGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK"; + private final String sharedPreferencesName; + private final String sharedPreferencesKey; private final Cipher cipher; private final SecureRandom secureRandom; private final Key secretKey; - public StorageCipherImplementationAES18(Context context, KeyCipher rsaCipher, Cipher ignoredStorageCipher) throws Exception { + public StorageCipherImplementationAES18(Context context, KeyCipher rsaCipher, Cipher ignoredStorageCipher, FlutterSecureStorageConfig config) throws Exception { secureRandom = new SecureRandom(); - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + // Backward compatibility: use original storage names for default config + if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) { + this.sharedPreferencesName = "FlutterSecureKeyStorage"; + this.sharedPreferencesKey = "VGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK"; + } else { + String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix(); + this.sharedPreferencesName = "FlutterSecureKeyStorage_" + configId; + this.sharedPreferencesKey = "VGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK_" + configId; + } + + SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); - String aesKey = preferences.getString(SHARED_PREFERENCES_KEY, null); + String aesKey = preferences.getString(sharedPreferencesKey, null); cipher = getCipher(); @@ -44,14 +56,14 @@ public StorageCipherImplementationAES18(Context context, KeyCipher rsaCipher, Ci secretKey = new SecretKeySpec(key, KEY_ALGORITHM); byte[] encryptedKey = rsaCipher.wrap(secretKey); - editor.putString(SHARED_PREFERENCES_KEY, Base64.encodeToString(encryptedKey, Base64.DEFAULT)); + editor.putString(sharedPreferencesKey, Base64.encodeToString(encryptedKey, Base64.DEFAULT)); editor.apply(); } @Override public void deleteKey(Context context) { - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - preferences.edit().remove(SHARED_PREFERENCES_KEY).apply(); + SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE); + preferences.edit().remove(sharedPreferencesKey).apply(); } protected Cipher getCipher() throws Exception { diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationAES23.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationAES23.java index df0e3a56..882a544a 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationAES23.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationAES23.java @@ -4,6 +4,8 @@ import android.content.SharedPreferences; import android.util.Base64; +import com.it_nomads.fluttersecurestorage.FlutterSecureStorageConfig; + import java.security.Key; import java.security.SecureRandom; @@ -17,14 +19,25 @@ public class StorageCipherImplementationAES23 implements StorageCipher { private static final int defaultIvSize = 12; private static final int AUTHENTICATION_TAG_SIZE = 128; private static final String KEY_ALGORITHM = "AES"; - private static final String SHARED_PREFERENCES_NAME = "FlutterSecureKeyStorage"; - private static final String KEYSTORE_IV_NAME = "BVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK"; + private final String sharedPreferencesName; + private final String keystoreIvName; private final Cipher cipher; private final SecureRandom secureRandom; private final Key secretKey; - public StorageCipherImplementationAES23(Context context, KeyCipher ignoredKeyCipher, Cipher cipher) throws Exception{ + public StorageCipherImplementationAES23(Context context, KeyCipher ignoredKeyCipher, Cipher cipher, FlutterSecureStorageConfig config) throws Exception{ secureRandom = new SecureRandom(); + + // Backward compatibility: use original storage names for default config + if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) { + this.sharedPreferencesName = "FlutterSecureKeyStorage"; + this.keystoreIvName = "BVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK"; + } else { + String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix(); + this.sharedPreferencesName = "FlutterSecureKeyStorage_" + configId; + this.keystoreIvName = "BVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK_" + configId; + } + this.secretKey = loadOrGenerateApplicationKey(context, cipher); this.cipher = getCipher(); } @@ -32,8 +45,8 @@ public StorageCipherImplementationAES23(Context context, KeyCipher ignoredKeyCip private SecretKey loadOrGenerateApplicationKey(Context context, Cipher biometricCipher) throws Exception { final Cipher cipher = (biometricCipher != null) ? biometricCipher : getCipher(); assert (cipher != null); - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - String encryptedAppKeyBase64 = preferences.getString(KEYSTORE_IV_NAME, null); + SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE); + String encryptedAppKeyBase64 = preferences.getString(keystoreIvName, null); if (encryptedAppKeyBase64 != null) { // Decrypt existing key - may throw BadPaddingException, IllegalBlockSizeException if algorithm changed @@ -48,7 +61,7 @@ private SecretKey loadOrGenerateApplicationKey(Context context, Cipher biometric byte[] newEncryptedAppKey = cipher.doFinal(appKey); SharedPreferences.Editor editor = preferences.edit(); - editor.putString(KEYSTORE_IV_NAME, Base64.encodeToString(newEncryptedAppKey, Base64.DEFAULT)); + editor.putString(keystoreIvName, Base64.encodeToString(newEncryptedAppKey, Base64.DEFAULT)); editor.apply(); return secretKey; @@ -56,8 +69,8 @@ private SecretKey loadOrGenerateApplicationKey(Context context, Cipher biometric @Override public void deleteKey(Context context) { - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - preferences.edit().remove(KEYSTORE_IV_NAME).apply(); + SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE); + preferences.edit().remove(keystoreIvName).apply(); } protected Cipher getCipher() throws Exception { diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationGCM.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationGCM.java index 549036a4..2669797e 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationGCM.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationGCM.java @@ -4,6 +4,8 @@ import android.content.SharedPreferences; import android.util.Base64; +import com.it_nomads.fluttersecurestorage.FlutterSecureStorageConfig; + import java.security.Key; import java.security.SecureRandom; import java.security.spec.AlgorithmParameterSpec; @@ -16,19 +18,29 @@ public class StorageCipherImplementationGCM implements StorageCipher { private static final int keySize = 16; private static final int AUTHENTICATION_TAG_SIZE = 128; private static final String KEY_ALGORITHM = "AES"; - private static final String SHARED_PREFERENCES_NAME = "FlutterSecureKeyStorage"; - private static final String SHARED_PREFERENCES_KEY = "AESVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK"; + private final String sharedPreferencesName; + private final String sharedPreferencesKey; private final Cipher cipher; private final SecureRandom secureRandom; private final Key secretKey; - public StorageCipherImplementationGCM(Context context, KeyCipher rsaCipher, Cipher ignoredCipher) throws Exception { + public StorageCipherImplementationGCM(Context context, KeyCipher rsaCipher, Cipher ignoredCipher, FlutterSecureStorageConfig config) throws Exception { secureRandom = new SecureRandom(); - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + // Backward compatibility: use original storage names for default config + if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) { + this.sharedPreferencesName = "FlutterSecureKeyStorage"; + this.sharedPreferencesKey = "AESVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK"; + } else { + String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix(); + this.sharedPreferencesName = "FlutterSecureKeyStorage_" + configId; + this.sharedPreferencesKey = "AESVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK_" + configId; + } + + SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); - String aesKey = preferences.getString(SHARED_PREFERENCES_KEY, null); + String aesKey = preferences.getString(sharedPreferencesKey, null); cipher = getCipher(); @@ -45,14 +57,14 @@ public StorageCipherImplementationGCM(Context context, KeyCipher rsaCipher, Ciph secretKey = new SecretKeySpec(key, KEY_ALGORITHM); byte[] encryptedKey = rsaCipher.wrap(secretKey); - editor.putString(SHARED_PREFERENCES_KEY, Base64.encodeToString(encryptedKey, Base64.DEFAULT)); + editor.putString(sharedPreferencesKey, Base64.encodeToString(encryptedKey, Base64.DEFAULT)); editor.apply(); } @Override public void deleteKey(Context context) { - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - preferences.edit().remove(SHARED_PREFERENCES_KEY).apply(); + SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE); + preferences.edit().remove(sharedPreferencesKey).apply(); } protected Cipher getCipher() throws Exception { From c69fb4fa0e3d9168540f97b2614a40e3ac2f3de4 Mon Sep 17 00:00:00 2001 From: Igor Kharakhordin Date: Tue, 23 Dec 2025 22:58:06 +0100 Subject: [PATCH 5/6] [Android] Set invalidatedByBiometricEnrollment to false --- flutter_secure_storage/CHANGELOG.md | 1 + .../ciphers/KeyCipherImplementationAES23.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/flutter_secure_storage/CHANGELOG.md b/flutter_secure_storage/CHANGELOG.md index 717bf22b..c0f1a3fd 100644 --- a/flutter_secure_storage/CHANGELOG.md +++ b/flutter_secure_storage/CHANGELOG.md @@ -3,6 +3,7 @@ * [Android] Enabled StrongBox by default, use fallback if it's not available. * [Android] Method to check if an Android device supports Strongbox * [Android] Use old algorithms as default (migration to AES_GCM_NoPadding is broken and fails) +* [Android] Set invalidatedByBiometricEnrollment to false * [Android] Create separate instances of FlutterSecureStorage with different configs/options * [Android] Use separate keys for different storage instances * [iOS] Add option to use secure enclave (based on [#989 PR](https://github.com/juliansteenbakker/flutter_secure_storage/pull/989)) diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationAES23.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationAES23.java index e2d22726..907ab0b5 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationAES23.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationAES23.java @@ -184,7 +184,7 @@ public void generateSymmetricKey() throws Exception { configureLegacyAuth(builder); } - builder.setInvalidatedByBiometricEnrollment(true); + builder.setInvalidatedByBiometricEnrollment(false); } else { // Explicitly set to false for clarity (default behavior) builder.setUserAuthenticationRequired(false); @@ -229,7 +229,7 @@ public void generateSymmetricKey() throws Exception { configureLegacyAuth(builder); } - builder.setInvalidatedByBiometricEnrollment(true); + builder.setInvalidatedByBiometricEnrollment(false); } keyGenerator.init(builder.build()); From e03abceb744046584e2e09abf7ecd03ba426f211 Mon Sep 17 00:00:00 2001 From: Igor Kharakhordin Date: Wed, 14 Jan 2026 13:05:04 +0100 Subject: [PATCH 6/6] [Android] Fix isStrongBoxSupported --- .../fluttersecurestorage/FlutterSecureStoragePlugin.java | 1 + 1 file changed, 1 insertion(+) diff --git a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java index 92083335..dc402d9c 100644 --- a/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java +++ b/flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/FlutterSecureStoragePlugin.java @@ -130,6 +130,7 @@ public void run() { if (call.method.equals("isStrongBoxSupported")) { result.success(isStrongBoxAvailable); + return; } FlutterSecureStorage secureStorage = initInstance(binding.getApplicationContext());