Skip to content

Commit

Permalink
feat: add new encrypted preferences together with old implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
juliansteenbakker committed Nov 18, 2024
1 parent 1a35312 commit 004cf6a
Show file tree
Hide file tree
Showing 7 changed files with 1,105 additions and 77 deletions.
12 changes: 6 additions & 6 deletions flutter_secure_storage/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:8.5.1'
classpath 'com.android.tools.build:gradle:8.5.2'
}
}

Expand All @@ -30,19 +30,19 @@ android {
buildConfig = true
}

compileSdk 34
compileSdk 35

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

defaultConfig {
minSdkVersion 19
minSdkVersion 23
}

}

dependencies {
implementation "androidx.security:security-crypto:1.1.0-alpha06"
implementation("com.google.crypto.tink:tink-android:1.14.1")
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
import android.util.Log;

import androidx.annotation.RequiresApi;
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;

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;

import java.io.IOException;
import java.nio.charset.Charset;
Expand All @@ -33,7 +33,6 @@ public class FlutterSecureStorage {
private SharedPreferences preferences;
private StorageCipher storageCipher;
private StorageCipherFactory storageCipherFactory;
private Boolean failedToUseEncryptedSharedPreferences = false;

public FlutterSecureStorage(Context context, Map<String, Object> options) {
this.options = options;
Expand All @@ -52,14 +51,6 @@ boolean getResetOnError() {
return options.containsKey("resetOnError") && options.get("resetOnError").equals("true");
}

@SuppressWarnings({"ConstantConditions"})
private boolean getUseEncryptedSharedPreferences() {
if (failedToUseEncryptedSharedPreferences) {
return false;
}
return options.containsKey("encryptedSharedPreferences") && options.get("encryptedSharedPreferences").equals("true") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}

public boolean containsKey(String key) {
ensureInitialized();
return preferences.contains(key);
Expand All @@ -72,11 +63,7 @@ public String addPrefixToKey(String key) {
public String read(String key) throws Exception {
ensureInitialized();

String rawValue = preferences.getString(key, null);
if (getUseEncryptedSharedPreferences()) {
return rawValue;
}
return decodeRawValue(rawValue);
return preferences.getString(key, null);
}

@SuppressWarnings("unchecked")
Expand All @@ -90,14 +77,7 @@ public Map<String, String> readAll() throws Exception {
String keyWithPrefix = entry.getKey();
if (keyWithPrefix.contains(ELEMENT_PREFERENCES_KEY_PREFIX)) {
String key = entry.getKey().replaceFirst(ELEMENT_PREFERENCES_KEY_PREFIX + '_', "");
if (getUseEncryptedSharedPreferences()) {
all.put(key, entry.getValue());
} else {
String rawValue = entry.getValue();
String value = decodeRawValue(rawValue);

all.put(key, value);
}
all.put(key, entry.getValue());
}
}
return all;
Expand All @@ -108,12 +88,7 @@ public void write(String key, String value) throws Exception {

SharedPreferences.Editor editor = preferences.edit();

if (getUseEncryptedSharedPreferences()) {
editor.putString(key, value);
} else {
byte[] result = storageCipher.encrypt(value.getBytes(charset));
editor.putString(key, Base64.encodeToString(result, 0));
}
editor.putString(key, value);
editor.apply();
}

Expand All @@ -130,9 +105,6 @@ public void deleteAll() {

final SharedPreferences.Editor editor = preferences.edit();
editor.clear();
if (!getUseEncryptedSharedPreferences()) {
storageCipherFactory.storeCurrentAlgorithms(editor);
}
editor.apply();
}

Expand All @@ -145,49 +117,31 @@ protected void ensureOptions(){
ELEMENT_PREFERENCES_KEY_PREFIX = (String) options.get("preferencesKeyPrefix");
}
}


@SuppressWarnings({"ConstantConditions"})
private void ensureInitialized() {
// Check if already initialized.
// TODO: Disable for now because this will break mixed usage of secureSharedPreference
// if (preferences != null) return;

ensureOptions();

SharedPreferences nonEncryptedPreferences = applicationContext.getSharedPreferences(
SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE
);
if (storageCipher == null) {
try {
initStorageCipher(nonEncryptedPreferences);

} catch (Exception e) {
Log.e(TAG, "StorageCipher initialization failed", e);
}
}
if (getUseEncryptedSharedPreferences() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
preferences = initializeEncryptedSharedPreferencesManager(applicationContext);
checkAndMigrateToEncrypted(nonEncryptedPreferences, preferences);
} catch (Exception e) {
Log.e(TAG, "EncryptedSharedPreferences initialization failed", e);
preferences = nonEncryptedPreferences;
failedToUseEncryptedSharedPreferences = true;
}
} else {
preferences = nonEncryptedPreferences;
try {
preferences = initializeEncryptedSharedPreferencesManager(applicationContext);
checkAndMigrateToEncrypted(preferences);
} catch (Exception e) {
Log.e(TAG, "EncryptedSharedPreferences initialization failed", e);
}

}

private void initStorageCipher(SharedPreferences source) throws Exception {
storageCipherFactory = new StorageCipherFactory(source, options);
if (getUseEncryptedSharedPreferences()) {
storageCipher = storageCipherFactory.getSavedStorageCipher(applicationContext);
} else if (storageCipherFactory.requiresReEncryption()) {
reEncryptPreferences(storageCipherFactory, source);
} else {
storageCipher = storageCipherFactory.getCurrentStorageCipher(applicationContext);
}
storageCipher = storageCipherFactory.getSavedStorageCipher(applicationContext);
// if (getUseEncryptedSharedPreferences()) {
// storageCipher = storageCipherFactory.getSavedStorageCipher(applicationContext);
// } else if (storageCipherFactory.requiresReEncryption()) {
// reEncryptPreferences(storageCipherFactory, source);
// } else {
// storageCipher = storageCipherFactory.getCurrentStorageCipher(applicationContext);
// }
}

private void reEncryptPreferences(StorageCipherFactory storageCipherFactory, SharedPreferences source) throws Exception {
Expand Down Expand Up @@ -216,7 +170,20 @@ private void reEncryptPreferences(StorageCipherFactory storageCipherFactory, Sha
}
}

private void checkAndMigrateToEncrypted(SharedPreferences source, SharedPreferences target) {
private void checkAndMigrateToEncrypted(SharedPreferences target) {
SharedPreferences source = applicationContext.getSharedPreferences(
SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE
);
if (storageCipher == null) {
try {
initStorageCipher(source);

} catch (Exception e) {
Log.e(TAG, "StorageCipher initialization failed", e);
}
}

try {
for (Map.Entry<String, ?> entry : source.getAll().entrySet()) {
Object v = entry.getValue();
Expand All @@ -235,7 +202,6 @@ private void checkAndMigrateToEncrypted(SharedPreferences source, SharedPreferen
}
}

@RequiresApi(api = Build.VERSION_CODES.M)
private SharedPreferences initializeEncryptedSharedPreferencesManager(Context context) throws GeneralSecurityException, IOException {
MasterKey key = new MasterKey.Builder(context)
.setKeyGenParameterSpec(
Expand Down
Loading

0 comments on commit 004cf6a

Please sign in to comment.