diff --git a/flutter_secure_storage/CHANGELOG.md b/flutter_secure_storage/CHANGELOG.md index a5049640..c0f1a3fd 100644 --- a/flutter_secure_storage/CHANGELOG.md +++ b/flutter_secure_storage/CHANGELOG.md @@ -3,6 +3,10 @@ * [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)) ## 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..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 @@ -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")); } @@ -132,13 +133,19 @@ public void run() { return; } + FlutterSecureStorage secureStorage = initInstance(binding.getApplicationContext()); + if (secureStorage == null) { + result.error("Could not initialize FlutterSecureStorage", null, null); + return; + } + secureStorage.initialize(config, new SecurePreferencesCallback<>() { @Override 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 +157,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 +172,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); 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..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 @@ -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(); } @@ -167,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); @@ -212,7 +229,7 @@ public void generateSymmetricKey() throws Exception { configureLegacyAuth(builder); } - builder.setInvalidatedByBiometricEnrollment(true); + builder.setInvalidatedByBiometricEnrollment(false); } keyGenerator.init(builder.build()); 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 {