Skip to content

Commit

Permalink
imp: refactor plugin and method calls
Browse files Browse the repository at this point in the history
  • Loading branch information
juliansteenbakker committed Jan 3, 2025
1 parent 34ed56d commit 1a5ea81
Show file tree
Hide file tree
Showing 3 changed files with 229 additions and 238 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import android.util.Base64;
import android.util.Log;

import com.it_nomads.fluttersecurestorage.ciphers.StorageCipher;
import com.it_nomads.fluttersecurestorage.ciphers.StorageCipherFactory;
import com.it_nomads.fluttersecurestorage.crypto.EncryptedSharedPreferences;
import com.it_nomads.fluttersecurestorage.crypto.MasterKey;
Expand All @@ -20,159 +21,161 @@

public class FlutterSecureStorage {

private final String TAG = "SecureStorageAndroid";
private final Charset charset = StandardCharsets.UTF_8;
private static final String TAG = "SecureStorageAndroid";
private static final Charset CHARSET = StandardCharsets.UTF_8;
private static final String DEFAULT_PREF_NAME = "FlutterSecureStorage";
private static final String DEFAULT_KEY_PREFIX = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIHNlY3VyZSBzdG9yYWdlCg";

private final Context applicationContext;
protected String ELEMENT_PREFERENCES_KEY_PREFIX = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIHNlY3VyZSBzdG9yYWdlCg";
protected Map<String, Object> options;
private String SHARED_PREFERENCES_NAME = "FlutterSecureStorage";
private SharedPreferences preferences;
private final Map<String, Object> options;

private String sharedPreferencesName = DEFAULT_PREF_NAME;
private String preferencesKeyPrefix = DEFAULT_KEY_PREFIX;
private SharedPreferences encryptedPreferences;

public FlutterSecureStorage(Context context, Map<String, Object> options) {
this.applicationContext = context.getApplicationContext();
this.options = options;
applicationContext = context.getApplicationContext();
}

boolean getResetOnError() {
Object value = options.containsKey("resetOnError") ? options.get("resetOnError") : "false";
return String.valueOf(value).equals("true");
ensureOptions();
getEncryptedSharedPreferences();
}

public boolean containsKey(String key) {
ensureInitialized();
return preferences.contains(key);
}

public String addPrefixToKey(String key) {
return ELEMENT_PREFERENCES_KEY_PREFIX + "_" + key;
SharedPreferences preferences = getEncryptedSharedPreferences();
return preferences != null && preferences.contains(addPrefixToKey(key));
}

public String read(String key) {
ensureInitialized();

return preferences.getString(key, null);
}

@SuppressWarnings("unchecked")
public Map<String, String> readAll() {
ensureInitialized();

Map<String, String> raw = (Map<String, String>) preferences.getAll();

Map<String, String> all = new HashMap<>();
for (Map.Entry<String, String> entry : raw.entrySet()) {
String keyWithPrefix = entry.getKey();
if (keyWithPrefix.contains(ELEMENT_PREFERENCES_KEY_PREFIX)) {
String key = entry.getKey().replaceFirst(ELEMENT_PREFERENCES_KEY_PREFIX + '_', "");
all.put(key, entry.getValue());
}
}
return all;
SharedPreferences preferences = getEncryptedSharedPreferences();
return preferences != null ? preferences.getString(addPrefixToKey(key), null) : null;
}

public void write(String key, String value) {
ensureInitialized();

SharedPreferences.Editor editor = preferences.edit();

editor.putString(key, value);
editor.apply();
SharedPreferences preferences = getEncryptedSharedPreferences();
if (preferences != null) {
preferences.edit().putString(addPrefixToKey(key), value).apply();
}
}

public void delete(String key) {
ensureInitialized();

SharedPreferences.Editor editor = preferences.edit();
editor.remove(key);
editor.apply();
SharedPreferences preferences = getEncryptedSharedPreferences();
if (preferences != null) {
preferences.edit().remove(addPrefixToKey(key)).apply();
}
}

public void deleteAll() {
ensureInitialized();

final SharedPreferences.Editor editor = preferences.edit();
editor.clear();
editor.apply();
}

protected void ensureOptions() {
String sharedPreferencesName = getStringOption("sharedPreferencesName");
if (!sharedPreferencesName.isEmpty()) {
SHARED_PREFERENCES_NAME = sharedPreferencesName;
SharedPreferences preferences = getEncryptedSharedPreferences();
if (preferences != null) {
preferences.edit().clear().apply();
}
}

String preferencesKeyPrefix = getStringOption("preferencesKeyPrefix");
if (!preferencesKeyPrefix.isEmpty()) {
ELEMENT_PREFERENCES_KEY_PREFIX = preferencesKeyPrefix;
public Map<String, String> readAll() {
SharedPreferences preferences = getEncryptedSharedPreferences();
Map<String, String> result = new HashMap<>();
if (preferences != null) {
Map<String, ?> allEntries = preferences.getAll();
for (Map.Entry<String, ?> entry : allEntries.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (key.startsWith(preferencesKeyPrefix) && value instanceof String) {
String originalKey = key.replaceFirst(preferencesKeyPrefix + "_", "");
result.put(originalKey, (String) value);
}
}
}
return result;
}

private String getStringOption(String key) {
Object value = options.get(key);
return value instanceof String ? (String) value : "";
private String addPrefixToKey(String key) {
return preferencesKeyPrefix + "_" + key;
}

private void ensureInitialized() {
ensureOptions();
private void ensureOptions() {
sharedPreferencesName = options.containsKey("sharedPreferencesName") && options.get("sharedPreferencesName") instanceof String
? (String) options.get("sharedPreferencesName")
: DEFAULT_PREF_NAME;

try {
preferences = initializeEncryptedSharedPreferencesManager(applicationContext);
checkAndMigrateToEncrypted(preferences);
} catch (Exception e) {
Log.e(TAG, "EncryptedSharedPreferences initialization failed", e);
}
preferencesKeyPrefix = options.containsKey("preferencesKeyPrefix") && options.get("preferencesKeyPrefix") instanceof String
? (String) options.get("preferencesKeyPrefix")
: DEFAULT_KEY_PREFIX;
}

private void checkAndMigrateToEncrypted(SharedPreferences target) {
SharedPreferences source = applicationContext.getSharedPreferences(
SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE
);
try {
final var storageCipherFactory = new StorageCipherFactory(source, options);
final var storageCipher = storageCipherFactory.getSavedStorageCipher(applicationContext);

private SharedPreferences getEncryptedSharedPreferences() {
if (encryptedPreferences == null) {
try {
for (Map.Entry<String, ?> entry : source.getAll().entrySet()) {
Object v = entry.getValue();
String key = entry.getKey();
if (v instanceof String && key.contains(ELEMENT_PREFERENCES_KEY_PREFIX)) {

byte[] data = Base64.decode((String) v, 0);
byte[] result = storageCipher.decrypt(data);

final String decodedValue = new String(result, charset);

target.edit().putString(key, (decodedValue)).apply();
source.edit().remove(key).apply();
}
}
final SharedPreferences.Editor sourceEditor = source.edit();
storageCipherFactory.removeCurrentAlgorithms(sourceEditor);
sourceEditor.apply();
encryptedPreferences = initializeEncryptedSharedPreferencesManager(applicationContext);
migrateToEncryptedPreferences(encryptedPreferences);
} catch (Exception e) {
Log.e(TAG, "Data migration failed", e);
Log.e(TAG, "EncryptedSharedPreferences initialization failed", e);
}
} catch (Exception e) {
Log.e(TAG, "StorageCipher initialization failed", e);
} else {
return encryptedPreferences;
}
return null;
}

private SharedPreferences initializeEncryptedSharedPreferencesManager(Context context) throws GeneralSecurityException, IOException {
MasterKey key = new MasterKey.Builder(context)
.setKeyGenParameterSpec(
new KeyGenParameterSpec
.Builder(MasterKey.DEFAULT_MASTER_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setKeySize(256).build())
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyGenParameterSpec(new KeyGenParameterSpec.Builder(
MasterKey.DEFAULT_MASTER_KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build())
.build();

return EncryptedSharedPreferences.create(
context,
SHARED_PREFERENCES_NAME,
key,
sharedPreferencesName,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
}

private void migrateToEncryptedPreferences(SharedPreferences target) {
SharedPreferences source = applicationContext.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);

try {
StorageCipher cipher = new StorageCipherFactory(source, options).getSavedStorageCipher(applicationContext);

Map<String, ?> sourceEntries = source.getAll();
if (sourceEntries.isEmpty()) return;

int succesfull = 0;
int failed = 0;
for (Map.Entry<String, ?> entry : sourceEntries.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (key.startsWith(preferencesKeyPrefix) && value instanceof String) {
try {
String decryptedValue = decryptValue((String) value, cipher);
target.edit().putString(key, decryptedValue).apply();
source.edit().remove(key).apply();
succesfull++;
} catch (Exception e) {
Log.e(TAG, "Migration failed for key: " + key, e);
failed++;
}
}
}

if (succesfull > 0) {
Log.i(TAG, "Succesfully migrated " + succesfull + " keys.");
}
if (failed > 0) {
Log.i(TAG, "Failed to migrate " + failed + " keys.");
}
} catch(Exception e) {
Log.e(TAG, "Migration failed due to initialisation error.", e);
}
}

private String decryptValue(String value, StorageCipher cipher) throws Exception {
byte[] data = Base64.decode(value, Base64.DEFAULT);
return new String(cipher.decrypt(data), CHARSET);
}
}
Loading

0 comments on commit 1a5ea81

Please sign in to comment.