22
33import android .content .Context ;
44import android .content .SharedPreferences ;
5+ import android .os .Build ;
56import android .security .keystore .KeyGenParameterSpec ;
67import android .security .keystore .KeyProperties ;
8+ import android .security .keystore .StrongBoxUnavailableException ;
79import android .util .Base64 ;
810import android .util .Log ;
911
@@ -31,6 +33,7 @@ public class FlutterSecureStorage {
3133 private static final String PREF_OPTION_PREFIX = "preferencesKeyPrefix" ;
3234 private static final String PREF_OPTION_DELETE_ON_FAILURE = "resetOnError" ;
3335 private static final String PREF_KEY_MIGRATED = "preferencesMigrated" ;
36+ private static final String PREF_OPTION_ONLY_ALLOW_STRONGBOX = "onlyAllowStrongBox" ;
3437 @ NonNull
3538 private final SharedPreferences encryptedPreferences ;
3639 @ NonNull
@@ -61,7 +64,15 @@ public FlutterSecureStorage(Context context, Map<String, Object> options) throws
6164 }
6265 }
6366
64- encryptedPreferences = getEncryptedSharedPreferences (deleteOnFailure , options , context .getApplicationContext (), sharedPreferencesName );
67+ boolean onlyAllowStrongbox = false ;
68+ if (options .containsKey (PREF_OPTION_ONLY_ALLOW_STRONGBOX )) {
69+ var value = options .get (PREF_OPTION_ONLY_ALLOW_STRONGBOX );
70+ if (value instanceof String ) {
71+ onlyAllowStrongbox = Boolean .parseBoolean ((String ) value );
72+ }
73+ }
74+
75+ encryptedPreferences = getEncryptedSharedPreferences (deleteOnFailure , options , context .getApplicationContext (), sharedPreferencesName , true , onlyAllowStrongbox );
6576 }
6677
6778 public boolean containsKey (String key ) {
@@ -83,6 +94,7 @@ public void delete(String key) {
8394 public void deleteAll () {
8495 encryptedPreferences .edit ().clear ().apply ();
8596 }
97+
8698
8799 public Map <String , String > readAll () {
88100 Map <String , String > result = new HashMap <>();
@@ -102,15 +114,25 @@ private String addPrefixToKey(String key) {
102114 return preferencesKeyPrefix + "_" + key ;
103115 }
104116
105- private SharedPreferences getEncryptedSharedPreferences (boolean deleteOnFailure , Map <String , Object > options , Context context , String sharedPreferencesName ) throws GeneralSecurityException , IOException {
117+ private SharedPreferences getEncryptedSharedPreferences (boolean deleteOnFailure , Map <String , Object > options , Context context , String sharedPreferencesName , boolean isStrongBoxBacked , boolean isOnlyStrongBoxAllowed ) throws GeneralSecurityException , IOException {
106118 try {
107- final SharedPreferences encryptedPreferences = initializeEncryptedSharedPreferencesManager (context , sharedPreferencesName );
119+ final SharedPreferences encryptedPreferences = initializeEncryptedSharedPreferencesManager (context , sharedPreferencesName , isStrongBoxBacked );
108120 boolean migrated = encryptedPreferences .getBoolean (PREF_KEY_MIGRATED , false );
109121 if (!migrated ) {
110122 migrateToEncryptedPreferences (context , sharedPreferencesName , encryptedPreferences , deleteOnFailure , options );
111123 }
112124 return encryptedPreferences ;
113125 } catch (GeneralSecurityException | IOException e ) {
126+ if (e instanceof GeneralSecurityException ) {
127+ Throwable cause = e .getCause ();
128+
129+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .P ) {
130+ if (cause instanceof StrongBoxUnavailableException && !isOnlyStrongBoxAllowed ) {
131+ // Fallback to not using Strongbox
132+ return getEncryptedSharedPreferences (deleteOnFailure , options , context , sharedPreferencesName , false , isOnlyStrongBoxAllowed );
133+ }
134+ }
135+ }
114136
115137 if (!deleteOnFailure ) {
116138 Log .w (TAG , "initialization failed, resetOnError false, so throwing exception." , e );
@@ -121,24 +143,29 @@ private SharedPreferences getEncryptedSharedPreferences(boolean deleteOnFailure,
121143 context .getSharedPreferences (sharedPreferencesName , Context .MODE_PRIVATE ).edit ().clear ().apply ();
122144
123145 try {
124- return initializeEncryptedSharedPreferencesManager (context , sharedPreferencesName );
146+ return initializeEncryptedSharedPreferencesManager (context , sharedPreferencesName , isStrongBoxBacked );
125147 } catch (Exception f ) {
126148 Log .e (TAG , "initialization after reset failed" , f );
127149 throw f ;
128150 }
129151 }
130152 }
131153
132- private SharedPreferences initializeEncryptedSharedPreferencesManager (Context context , String sharedPreferencesName ) throws GeneralSecurityException , IOException {
154+ private SharedPreferences initializeEncryptedSharedPreferencesManager (Context context , String sharedPreferencesName , boolean isStrongBoxBacked ) throws GeneralSecurityException , IOException {
155+ KeyGenParameterSpec .Builder keyGenBuilder = new KeyGenParameterSpec .Builder (
156+ MasterKey .DEFAULT_MASTER_KEY_ALIAS ,
157+ KeyProperties .PURPOSE_ENCRYPT | KeyProperties .PURPOSE_DECRYPT )
158+ .setBlockModes (KeyProperties .BLOCK_MODE_GCM )
159+ .setEncryptionPaddings (KeyProperties .ENCRYPTION_PADDING_NONE )
160+ .setKeySize (256 );
161+
162+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .P && isStrongBoxBacked ) {
163+ keyGenBuilder .setIsStrongBoxBacked (true );
164+ }
165+
133166 MasterKey masterKey = new MasterKey .Builder (context )
134- .setKeyGenParameterSpec (new KeyGenParameterSpec .Builder (
135- MasterKey .DEFAULT_MASTER_KEY_ALIAS ,
136- KeyProperties .PURPOSE_ENCRYPT | KeyProperties .PURPOSE_DECRYPT )
137- .setBlockModes (KeyProperties .BLOCK_MODE_GCM )
138- .setEncryptionPaddings (KeyProperties .ENCRYPTION_PADDING_NONE )
139- .setKeySize (256 )
140- .build ())
141- .build ();
167+ .setKeyGenParameterSpec (keyGenBuilder .build ())
168+ .build (isStrongBoxBacked );
142169
143170 return EncryptedSharedPreferences .create (
144171 context ,
0 commit comments