Skip to content

Commit f182d2c

Browse files
committed
[Android] Use separate keys for different storage instances
1 parent dff9009 commit f182d2c

9 files changed

Lines changed: 107 additions & 38 deletions

flutter_secure_storage/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* [Android] (Feature) Method to check if an Android device supports Strongbox
44
* [Android] (Fix) Create separate instances of FlutterSecureStorage with different configs/options
5+
* [Android] (Fix) Use separate keys for different storage instances
56
* [Android] (Adjustment) Enabled StrongBox by default, use fallback if it's not available
67
* [Android] (Adjustment) Set invalidatedByBiometricEnrollment to false
78
* [iOS] (Feature) Add option to use secure enclave (based on [#989 PR](https://github.com/juliansteenbakker/flutter_secure_storage/pull/989))

flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationAES23.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ class KeyCipherImplementationAES23 implements KeyCipher {
2929

3030
private static final String TAG = "AESCipher23";
3131
private static final String KEYSTORE_PROVIDER_ANDROID = "AndroidKeyStore";
32-
private static final String SHARED_PREFERENCES_NAME = "FlutterSecureKeyStorage";
33-
private static final String SHARED_PREFERENCES_KEY = "KeyStoreIV1";
3432
private static final int IV_SIZE = 16;
3533
private static final int KEY_SIZE = 256;
3634
protected final String keyAlias;
35+
protected final String ivStorageKey;
36+
protected final String ivStoragePrefsName;
3737

3838
protected final Context context;
3939
protected final FlutterSecureStorageConfig config;
@@ -42,6 +42,17 @@ public KeyCipherImplementationAES23(Context context, FlutterSecureStorageConfig
4242
this.context = context;
4343
this.config = config;
4444
keyAlias = createKeyAlias(context);
45+
46+
// Backward compatibility: use original storage names for default config
47+
if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) {
48+
ivStoragePrefsName = "FlutterSecureKeyStorage";
49+
ivStorageKey = "KeyStoreIV1";
50+
} else {
51+
String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix();
52+
ivStoragePrefsName = "FlutterSecureKeyStorage_" + configId;
53+
ivStorageKey = "KeyStoreIV1_" + configId;
54+
}
55+
4556
KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID);
4657
ks.load(null);
4758
Key privateKey = ks.getKey(keyAlias, null);
@@ -61,7 +72,13 @@ public Key unwrap(byte[] wrappedKey, String algorithm) throws UnsupportedOperati
6172
}
6273

6374
protected String createKeyAlias(Context context) {
64-
return context.getPackageName() + ".FlutterSecureStoragePluginKey";
75+
// Backward compatibility: use original key name for default config
76+
if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) {
77+
return context.getPackageName() + ".FlutterSecureStoragePluginKey";
78+
}
79+
80+
String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix();
81+
return context.getPackageName() + ".FlutterSecureStoragePluginKey_" + configId;
6582
}
6683

6784
@Override
@@ -70,8 +87,8 @@ public void deleteKey() throws Exception {
7087
ks.load(null);
7188
ks.deleteEntry(keyAlias);
7289

73-
SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
74-
preferences.edit().remove(SHARED_PREFERENCES_KEY).apply();
90+
SharedPreferences preferences = context.getSharedPreferences(ivStoragePrefsName, Context.MODE_PRIVATE);
91+
preferences.edit().remove(ivStorageKey).apply();
7592
}
7693

7794
@Override
@@ -90,8 +107,8 @@ public Cipher getCipher(Context context) throws Exception {
90107

91108
public Cipher getEncryptionCipher(Context context, Key key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
92109
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
93-
SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
94-
String ivBase64 = preferences.getString(SHARED_PREFERENCES_KEY, null);
110+
SharedPreferences preferences = context.getSharedPreferences(ivStoragePrefsName, Context.MODE_PRIVATE);
111+
String ivBase64 = preferences.getString(ivStorageKey, null);
95112

96113
if (ivBase64 != null) {
97114
byte[] iv = Base64.decode(ivBase64, Base64.DEFAULT);
@@ -103,7 +120,7 @@ public Cipher getEncryptionCipher(Context context, Key key) throws NoSuchPadding
103120

104121
byte[] iv = cipher.getIV();
105122
SharedPreferences.Editor editor = preferences.edit();
106-
editor.putString(SHARED_PREFERENCES_KEY, Base64.encodeToString(iv, Base64.DEFAULT));
123+
editor.putString(ivStorageKey, Base64.encodeToString(iv, Base64.DEFAULT));
107124
editor.apply();
108125
}
109126

flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSA18.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@ public KeyCipherImplementationRSA18(Context context, FlutterSecureStorageConfig
4141
}
4242

4343
protected String createKeyAlias() {
44-
return context.getPackageName() + ".FlutterSecureStoragePluginKey";
44+
// Backward compatibility: use original key name for default config
45+
if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) {
46+
return context.getPackageName() + ".FlutterSecureStoragePluginKey";
47+
}
48+
49+
String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix();
50+
return context.getPackageName() + ".FlutterSecureStoragePluginKey_" + configId;
4551
}
4652

4753
@Override

flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/KeyCipherImplementationRSAOAEP.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ public KeyCipherImplementationRSAOAEP(Context context, FlutterSecureStorageConfi
2727

2828
@Override
2929
protected String createKeyAlias() {
30-
return context.getPackageName() + ".FlutterSecureStoragePluginKeyOAEP";
30+
// Backward compatibility: use original key name for default config
31+
if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) {
32+
return context.getPackageName() + ".FlutterSecureStoragePluginKeyOAEP";
33+
}
34+
35+
String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix();
36+
return context.getPackageName() + ".FlutterSecureStoragePluginKeyOAEP_" + configId;
3137
}
3238

3339
@RequiresApi(api = Build.VERSION_CODES.M)

flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherFactory.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,18 @@ private StorageCipher createStorageCipher(Context context, KeyCipher keyCipher,
8282
if (algorithm == StorageCipherAlgorithm.AES_GCM_NoPadding) {
8383
if (isKeyStoreKeyCipher(keyCipher)) {
8484
// Use KeyStore-based implementation (biometric/PIN auth capable)
85-
return new StorageCipherImplementationAES23(context, keyCipher, cipher);
85+
return new StorageCipherImplementationAES23(context, keyCipher, cipher, config);
8686
} else {
8787
// Use RSA-wrapped implementation (standard secure storage)
88-
return new StorageCipherImplementationGCM(context, keyCipher, cipher);
88+
return new StorageCipherImplementationGCM(context, keyCipher, cipher, config);
8989
}
9090
}
9191

9292
// For other algorithms, use the function from enum
9393
if (algorithm.storageCipher == null) {
9494
throw new Exception("No implementation available for algorithm: " + algorithm.name());
9595
}
96-
return algorithm.storageCipher.apply(context, keyCipher, cipher);
96+
return algorithm.storageCipher.apply(context, keyCipher, cipher, config);
9797
}
9898

9999
/**

flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherFunction.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import android.content.Context;
44

5+
import com.it_nomads.fluttersecurestorage.FlutterSecureStorageConfig;
6+
57
import javax.crypto.Cipher;
68

79
@FunctionalInterface
810
interface StorageCipherFunction {
9-
StorageCipher apply(Context context, KeyCipher keyCipher, Cipher cipher) throws Exception;
11+
StorageCipher apply(Context context, KeyCipher keyCipher, Cipher cipher, FlutterSecureStorageConfig config) throws Exception;
1012
}

flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationAES18.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import android.content.SharedPreferences;
55
import android.util.Base64;
66

7+
import com.it_nomads.fluttersecurestorage.FlutterSecureStorageConfig;
8+
79
import java.security.Key;
810
import java.security.SecureRandom;
911
import java.security.spec.AlgorithmParameterSpec;
@@ -15,19 +17,29 @@
1517
public class StorageCipherImplementationAES18 implements StorageCipher {
1618
private static final int keySize = 16;
1719
private static final String KEY_ALGORITHM = "AES";
18-
private static final String SHARED_PREFERENCES_NAME = "FlutterSecureKeyStorage";
19-
private static final String SHARED_PREFERENCES_KEY = "VGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK";
20+
private final String sharedPreferencesName;
21+
private final String sharedPreferencesKey;
2022
private final Cipher cipher;
2123
private final SecureRandom secureRandom;
2224
private final Key secretKey;
2325

24-
public StorageCipherImplementationAES18(Context context, KeyCipher rsaCipher, Cipher ignoredStorageCipher) throws Exception {
26+
public StorageCipherImplementationAES18(Context context, KeyCipher rsaCipher, Cipher ignoredStorageCipher, FlutterSecureStorageConfig config) throws Exception {
2527
secureRandom = new SecureRandom();
2628

27-
SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
29+
// Backward compatibility: use original storage names for default config
30+
if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) {
31+
this.sharedPreferencesName = "FlutterSecureKeyStorage";
32+
this.sharedPreferencesKey = "VGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK";
33+
} else {
34+
String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix();
35+
this.sharedPreferencesName = "FlutterSecureKeyStorage_" + configId;
36+
this.sharedPreferencesKey = "VGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK_" + configId;
37+
}
38+
39+
SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);
2840
SharedPreferences.Editor editor = preferences.edit();
2941

30-
String aesKey = preferences.getString(SHARED_PREFERENCES_KEY, null);
42+
String aesKey = preferences.getString(sharedPreferencesKey, null);
3143

3244
cipher = getCipher();
3345

@@ -44,14 +56,14 @@ public StorageCipherImplementationAES18(Context context, KeyCipher rsaCipher, Ci
4456
secretKey = new SecretKeySpec(key, KEY_ALGORITHM);
4557

4658
byte[] encryptedKey = rsaCipher.wrap(secretKey);
47-
editor.putString(SHARED_PREFERENCES_KEY, Base64.encodeToString(encryptedKey, Base64.DEFAULT));
59+
editor.putString(sharedPreferencesKey, Base64.encodeToString(encryptedKey, Base64.DEFAULT));
4860
editor.apply();
4961
}
5062

5163
@Override
5264
public void deleteKey(Context context) {
53-
SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
54-
preferences.edit().remove(SHARED_PREFERENCES_KEY).apply();
65+
SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);
66+
preferences.edit().remove(sharedPreferencesKey).apply();
5567
}
5668

5769
protected Cipher getCipher() throws Exception {

flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationAES23.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import android.content.SharedPreferences;
55
import android.util.Base64;
66

7+
import com.it_nomads.fluttersecurestorage.FlutterSecureStorageConfig;
8+
79
import java.security.Key;
810
import java.security.SecureRandom;
911

@@ -17,23 +19,34 @@ public class StorageCipherImplementationAES23 implements StorageCipher {
1719
private static final int defaultIvSize = 12;
1820
private static final int AUTHENTICATION_TAG_SIZE = 128;
1921
private static final String KEY_ALGORITHM = "AES";
20-
private static final String SHARED_PREFERENCES_NAME = "FlutterSecureKeyStorage";
21-
private static final String KEYSTORE_IV_NAME = "BVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK";
22+
private final String sharedPreferencesName;
23+
private final String keystoreIvName;
2224
private final Cipher cipher;
2325
private final SecureRandom secureRandom;
2426
private final Key secretKey;
2527

26-
public StorageCipherImplementationAES23(Context context, KeyCipher ignoredKeyCipher, Cipher cipher) throws Exception{
28+
public StorageCipherImplementationAES23(Context context, KeyCipher ignoredKeyCipher, Cipher cipher, FlutterSecureStorageConfig config) throws Exception{
2729
secureRandom = new SecureRandom();
30+
31+
// Backward compatibility: use original storage names for default config
32+
if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) {
33+
this.sharedPreferencesName = "FlutterSecureKeyStorage";
34+
this.keystoreIvName = "BVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK";
35+
} else {
36+
String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix();
37+
this.sharedPreferencesName = "FlutterSecureKeyStorage_" + configId;
38+
this.keystoreIvName = "BVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK_" + configId;
39+
}
40+
2841
this.secretKey = loadOrGenerateApplicationKey(context, cipher);
2942
this.cipher = getCipher();
3043
}
3144

3245
private SecretKey loadOrGenerateApplicationKey(Context context, Cipher biometricCipher) throws Exception {
3346
final Cipher cipher = (biometricCipher != null) ? biometricCipher : getCipher();
3447
assert (cipher != null);
35-
SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
36-
String encryptedAppKeyBase64 = preferences.getString(KEYSTORE_IV_NAME, null);
48+
SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);
49+
String encryptedAppKeyBase64 = preferences.getString(keystoreIvName, null);
3750

3851
if (encryptedAppKeyBase64 != null) {
3952
// Decrypt existing key - may throw BadPaddingException, IllegalBlockSizeException if algorithm changed
@@ -48,16 +61,16 @@ private SecretKey loadOrGenerateApplicationKey(Context context, Cipher biometric
4861
byte[] newEncryptedAppKey = cipher.doFinal(appKey);
4962

5063
SharedPreferences.Editor editor = preferences.edit();
51-
editor.putString(KEYSTORE_IV_NAME, Base64.encodeToString(newEncryptedAppKey, Base64.DEFAULT));
64+
editor.putString(keystoreIvName, Base64.encodeToString(newEncryptedAppKey, Base64.DEFAULT));
5265
editor.apply();
5366

5467
return secretKey;
5568
}
5669

5770
@Override
5871
public void deleteKey(Context context) {
59-
SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
60-
preferences.edit().remove(KEYSTORE_IV_NAME).apply();
72+
SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);
73+
preferences.edit().remove(keystoreIvName).apply();
6174
}
6275

6376
protected Cipher getCipher() throws Exception {

flutter_secure_storage/android/src/main/java/com/it_nomads/fluttersecurestorage/ciphers/StorageCipherImplementationGCM.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import android.content.SharedPreferences;
55
import android.util.Base64;
66

7+
import com.it_nomads.fluttersecurestorage.FlutterSecureStorageConfig;
8+
79
import java.security.Key;
810
import java.security.SecureRandom;
911
import java.security.spec.AlgorithmParameterSpec;
@@ -16,19 +18,29 @@ public class StorageCipherImplementationGCM implements StorageCipher {
1618
private static final int keySize = 16;
1719
private static final int AUTHENTICATION_TAG_SIZE = 128;
1820
private static final String KEY_ALGORITHM = "AES";
19-
private static final String SHARED_PREFERENCES_NAME = "FlutterSecureKeyStorage";
20-
private static final String SHARED_PREFERENCES_KEY = "AESVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK";
21+
private final String sharedPreferencesName;
22+
private final String sharedPreferencesKey;
2123
private final Cipher cipher;
2224
private final SecureRandom secureRandom;
2325
private final Key secretKey;
2426

25-
public StorageCipherImplementationGCM(Context context, KeyCipher rsaCipher, Cipher ignoredCipher) throws Exception {
27+
public StorageCipherImplementationGCM(Context context, KeyCipher rsaCipher, Cipher ignoredCipher, FlutterSecureStorageConfig config) throws Exception {
2628
secureRandom = new SecureRandom();
2729

28-
SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
30+
// Backward compatibility: use original storage names for default config
31+
if ("FlutterSecureStorage".equals(config.getSharedPreferencesName())) {
32+
this.sharedPreferencesName = "FlutterSecureKeyStorage";
33+
this.sharedPreferencesKey = "AESVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK";
34+
} else {
35+
String configId = config.getSharedPreferencesName() + "_" + config.getSharedPreferencesKeyPrefix();
36+
this.sharedPreferencesName = "FlutterSecureKeyStorage_" + configId;
37+
this.sharedPreferencesKey = "AESVGhpcyBpcyB0aGUga2V5IGZvciBhIHNlY3VyZSBzdG9yYWdlIEFFUyBLZXkK_" + configId;
38+
}
39+
40+
SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);
2941
SharedPreferences.Editor editor = preferences.edit();
3042

31-
String aesKey = preferences.getString(SHARED_PREFERENCES_KEY, null);
43+
String aesKey = preferences.getString(sharedPreferencesKey, null);
3244

3345
cipher = getCipher();
3446

@@ -45,14 +57,14 @@ public StorageCipherImplementationGCM(Context context, KeyCipher rsaCipher, Ciph
4557
secretKey = new SecretKeySpec(key, KEY_ALGORITHM);
4658

4759
byte[] encryptedKey = rsaCipher.wrap(secretKey);
48-
editor.putString(SHARED_PREFERENCES_KEY, Base64.encodeToString(encryptedKey, Base64.DEFAULT));
60+
editor.putString(sharedPreferencesKey, Base64.encodeToString(encryptedKey, Base64.DEFAULT));
4961
editor.apply();
5062
}
5163

5264
@Override
5365
public void deleteKey(Context context) {
54-
SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
55-
preferences.edit().remove(SHARED_PREFERENCES_KEY).apply();
66+
SharedPreferences preferences = context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);
67+
preferences.edit().remove(sharedPreferencesKey).apply();
5668
}
5769

5870
protected Cipher getCipher() throws Exception {

0 commit comments

Comments
 (0)