diff --git a/encrypted-shared-prefs/.gitignore b/encrypted-shared-prefs/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/encrypted-shared-prefs/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/encrypted-shared-prefs/build.gradle b/encrypted-shared-prefs/build.gradle
new file mode 100644
index 0000000..085c337
--- /dev/null
+++ b/encrypted-shared-prefs/build.gradle
@@ -0,0 +1,24 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion versions.compileSdk
+
+ defaultConfig {
+ minSdkVersion 21
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+ implementation "androidx.core:core:$versions.androidx"
+ implementation "androidx.annotation:annotation:$versions.androidx"
+ implementation "androidx.appcompat:appcompat:$versions.appcompat"
+
+}
diff --git a/encrypted-shared-prefs/src/main/AndroidManifest.xml b/encrypted-shared-prefs/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a068e5f
--- /dev/null
+++ b/encrypted-shared-prefs/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/encrypted-shared-prefs/src/main/java/ru/touchin/roboswag/Extensions.kt b/encrypted-shared-prefs/src/main/java/ru/touchin/roboswag/Extensions.kt
new file mode 100644
index 0000000..a4305ed
--- /dev/null
+++ b/encrypted-shared-prefs/src/main/java/ru/touchin/roboswag/Extensions.kt
@@ -0,0 +1,9 @@
+package ru.touchin.roboswag
+
+import android.content.SharedPreferences
+
+fun TouchinSharedPreferences.migrateFromSharedPreferences(from: SharedPreferences, key: String): SharedPreferences {
+ if (!from.contains(key)) return this
+ edit().putString(key, from.getString(key, "") ?: "").apply()
+ return this
+}
diff --git a/encrypted-shared-prefs/src/main/java/ru/touchin/roboswag/TouchinSharedPreferences.kt b/encrypted-shared-prefs/src/main/java/ru/touchin/roboswag/TouchinSharedPreferences.kt
new file mode 100644
index 0000000..dae6b07
--- /dev/null
+++ b/encrypted-shared-prefs/src/main/java/ru/touchin/roboswag/TouchinSharedPreferences.kt
@@ -0,0 +1,103 @@
+package ru.touchin.roboswag
+
+import android.content.Context
+import android.content.SharedPreferences
+
+class TouchinSharedPreferences(name: String, context: Context, val isEncryption: Boolean = false) : SharedPreferences {
+
+ private val currentPreferences: SharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE)
+ private val cryptoUtils = TouchinSharedPreferencesCryptoUtils(context)
+
+ override fun contains(key: String?) = currentPreferences.contains(key)
+
+ override fun getBoolean(key: String?, defaultValue: Boolean) = get(key, defaultValue)
+
+ override fun unregisterOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener: SharedPreferences.OnSharedPreferenceChangeListener?) {
+ currentPreferences.unregisterOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
+ }
+
+ override fun getInt(key: String?, defaultValue: Int) = get(key, defaultValue)
+
+ override fun getAll(): MutableMap {
+ return if (isEncryption) {
+ currentPreferences.all.mapValues { it.value.toString().decrypt() }.toMutableMap()
+ } else {
+ currentPreferences.all.mapValues { it.value.toString() }.toMutableMap()
+ }
+ }
+
+ override fun edit() = TouchinEditor()
+
+ override fun getLong(key: String?, defaultValue: Long) = get(key, defaultValue)
+
+ override fun getFloat(key: String?, defaultValue: Float) = get(key, defaultValue)
+
+ override fun getString(key: String?, defaultValue: String?): String = get(key, defaultValue ?: "")
+
+ override fun getStringSet(key: String?, set: MutableSet?): MutableSet? {
+ return if (isEncryption) {
+ val value = currentPreferences.getStringSet(key, set)
+ if (value == set) {
+ set
+ } else {
+ value?.map { it.decrypt() }?.toMutableSet()
+ }
+ } else {
+ currentPreferences.getStringSet(key, set)
+ }
+ }
+
+ override fun registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener: SharedPreferences.OnSharedPreferenceChangeListener?) {
+ currentPreferences.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
+ }
+
+ private fun get(key: String?, defaultValue: T): T {
+ if (!currentPreferences.contains(key)) return defaultValue
+ val value = currentPreferences.getString(key, "")?.decrypt()
+ return when (defaultValue) {
+ is Boolean -> value?.toBoolean() as? T
+ is Long -> value?.toLong() as? T
+ is String -> value as? T
+ is Int -> value?.toInt() as? T
+ is Float -> value?.toFloat() as? T
+ else -> value as? T
+ } ?: defaultValue
+ }
+
+ private fun String.decrypt() = if (isEncryption) cryptoUtils.decrypt(this) else this
+
+ inner class TouchinEditor : SharedPreferences.Editor {
+
+ override fun clear() = currentPreferences.edit().clear()
+
+ override fun putLong(key: String?, value: Long) = put(key, value)
+
+ override fun putInt(key: String?, value: Int) = put(key, value)
+
+ override fun remove(key: String?) = currentPreferences.edit().remove(key)
+
+ override fun putBoolean(key: String?, value: Boolean) = put(key, value)
+
+ override fun putStringSet(key: String?, value: MutableSet?): SharedPreferences.Editor {
+ return if (isEncryption) {
+ currentPreferences.edit().putStringSet(key, value?.map { it.encrypt() }?.toMutableSet())
+ } else {
+ currentPreferences.edit().putStringSet(key, value)
+ }
+ }
+
+ override fun commit() = currentPreferences.edit().commit()
+
+ override fun putFloat(key: String?, value: Float) = put(key, value)
+
+ override fun apply() = currentPreferences.edit().apply()
+
+ override fun putString(key: String?, value: String?) = put(key, value)
+
+ private fun put(key: String?, value: T) = currentPreferences.edit().putString(key, value.toString().encrypt())
+
+ private fun String.encrypt() = if (isEncryption) cryptoUtils.encrypt(this) else this
+
+ }
+
+}
diff --git a/encrypted-shared-prefs/src/main/java/ru/touchin/roboswag/TouchinSharedPreferencesCryptoUtils.kt b/encrypted-shared-prefs/src/main/java/ru/touchin/roboswag/TouchinSharedPreferencesCryptoUtils.kt
new file mode 100644
index 0000000..2cc1b82
--- /dev/null
+++ b/encrypted-shared-prefs/src/main/java/ru/touchin/roboswag/TouchinSharedPreferencesCryptoUtils.kt
@@ -0,0 +1,115 @@
+package ru.touchin.roboswag
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.os.Build
+import android.security.KeyPairGeneratorSpec
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
+import android.util.Base64
+import java.math.BigInteger
+import java.security.KeyPair
+import java.security.KeyPairGenerator
+import java.security.KeyStore
+import java.security.PrivateKey
+import java.util.Calendar
+import javax.crypto.Cipher
+import javax.security.auth.x500.X500Principal
+
+// https://proandroiddev.com/secure-data-in-android-encryption-in-android-part-2-991a89e55a23
+@Suppress("detekt.TooGenericExceptionCaught")
+class TouchinSharedPreferencesCryptoUtils constructor(val context: Context) {
+
+ companion object {
+
+ private const val ANDROID_KEY_STORE = "AndroidKeyStore"
+ private const val STORAGE_KEY = "STORAGE_KEY"
+ private const val KEY_ALGORITHM_RSA = "RSA"
+ private const val TRANSFORMATION_ASYMMETRIC = "RSA/ECB/PKCS1Padding"
+
+ private fun getAndroidKeystore(): KeyStore? = try {
+ KeyStore.getInstance(ANDROID_KEY_STORE).also { it.load(null) }
+ } catch (exception: Exception) {
+ null
+ }
+
+ private fun getAndroidKeyStoreAsymmetricKeyPair(): KeyPair? {
+ val privateKey = getAndroidKeystore()?.getKey(STORAGE_KEY, null) as PrivateKey?
+ val publicKey = getAndroidKeystore()?.getCertificate(STORAGE_KEY)?.publicKey
+ return if (privateKey != null && publicKey != null) {
+ KeyPair(publicKey, privateKey)
+ } else {
+ null
+ }
+ }
+
+ private fun createAndroidKeyStoreAsymmetricKey(context: Context): KeyPair {
+ val generator = KeyPairGenerator.getInstance(KEY_ALGORITHM_RSA, ANDROID_KEY_STORE)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ initGeneratorWithKeyPairGeneratorSpec(generator)
+ } else {
+ initGeneratorWithKeyGenParameterSpec(generator, context)
+ }
+
+ // Generates Key with given spec and saves it to the KeyStore
+ return generator.generateKeyPair()
+ }
+
+ private fun initGeneratorWithKeyGenParameterSpec(generator: KeyPairGenerator, context: Context) {
+ val startDate = Calendar.getInstance()
+ val endDate = Calendar.getInstance()
+ endDate.add(Calendar.YEAR, 20)
+
+ val builder = KeyPairGeneratorSpec.Builder(context)
+ .setAlias(STORAGE_KEY)
+ .setSerialNumber(BigInteger.ONE)
+ .setSubject(X500Principal("CN=$STORAGE_KEY CA Certificate"))
+ .setStartDate(startDate.time)
+ .setEndDate(endDate.time)
+
+ generator.initialize(builder.build())
+ }
+
+ @TargetApi(Build.VERSION_CODES.M)
+ private fun initGeneratorWithKeyPairGeneratorSpec(generator: KeyPairGenerator) {
+ val builder = KeyGenParameterSpec.Builder(STORAGE_KEY, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
+ generator.initialize(builder.build())
+ }
+
+ private fun createCipher(): Cipher? = try {
+ Cipher.getInstance(TRANSFORMATION_ASYMMETRIC)
+ } catch (exception: Exception) {
+ null
+ }
+
+ }
+
+ private val cipher = createCipher()
+ private val keyPair = getAndroidKeyStoreAsymmetricKeyPair()
+ ?: createAndroidKeyStoreAsymmetricKey(context)
+
+ // Those methods should not take and return strings, only char[] and those arrays should be cleared right after usage
+ // See for explanation https://docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html#PBEEx
+
+ @Synchronized
+ fun encrypt(data: String): String {
+ cipher?.init(Cipher.ENCRYPT_MODE, keyPair.public)
+ val bytes = cipher?.doFinal(data.toByteArray())
+ return Base64.encodeToString(bytes, Base64.DEFAULT)
+ }
+
+ @Synchronized
+ fun decrypt(data: String?): String {
+ cipher?.init(Cipher.DECRYPT_MODE, keyPair.private)
+ if (data.isNullOrBlank()) {
+ return String()
+ }
+ val encryptedData = Base64.decode(data, Base64.DEFAULT)
+ val decodedData = cipher?.doFinal(encryptedData)
+ return decodedData?.let { decodedData -> String(decodedData) } ?: ""
+ }
+
+}
diff --git a/storable/build.gradle b/storable/build.gradle
index 2983f29..340a921 100644
--- a/storable/build.gradle
+++ b/storable/build.gradle
@@ -22,6 +22,4 @@ dependencies {
implementation "io.reactivex.rxjava2:rxjava:$versions.rxJava"
implementation "io.reactivex.rxjava2:rxandroid:$versions.rxAndroid"
-
- implementation "com.github.yandextaxitech:binaryprefs:$versions.binaryprefs"
}
diff --git a/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java
index f541878..1204a53 100644
--- a/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java
+++ b/storable/src/main/java/ru/touchin/roboswag/components/utils/storables/PreferenceUtils.java
@@ -21,9 +21,6 @@ package ru.touchin.roboswag.components.utils.storables;
import android.content.SharedPreferences;
-import com.ironz.binaryprefs.BinaryPreferencesBuilder;
-import com.ironz.binaryprefs.Preferences;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -58,24 +55,6 @@ public final class PreferenceUtils {
).build();
}
- /**
- * Creates {@link Storable} that stores string into {@link Preferences} from https://github.com/yandextaxitech/binaryprefs
- *
- * @param name Name of preference;
- * @param preferences Preferences to store value;
- * @return {@link Storable} for string.
- */
- @NonNull
- public static Storable binaryPreferencesStringStorable(@NonNull final String name, @NonNull final Preferences preferences) {
- return new Storable.Builder(
- name,
- String.class,
- String.class,
- new PreferenceStore<>(preferences),
- new SameTypesConverter<>()
- ).build();
- }
-
/**
* Creates {@link NonNullStorable} that stores string into {@link SharedPreferences} with default value.
*