Merge pull request #134 from TouchInstinct/enc-shared-prefs
Enc shared prefs
This commit is contained in:
commit
696d358d29
|
|
@ -21,7 +21,7 @@ Roboswag позволяет сочетать эти три решения в о
|
|||
### Работа с RecyclerView
|
||||
RecyclerView - один из самых часто используемых инструментов Android разработчика. Модуль [recyclerview-adapters](/recyclerview-adapters) позволяет сделать работу с RecyclerView более гибкой и делает работу самого элемента быстрее.
|
||||
### Работа с SharedPreferences
|
||||
Чтобы сохранять простые данные в память смартфона, используются SharedPreferences. Модуль [storable](/storable) разработан для облегчения работы с SharedPreferences.
|
||||
Чтобы сохранять простые данные в память смартфона, используются SharedPreferences. Модуль [storable](/storable) разработан для облегчения работы с SharedPreferences. Для шифрования данных в SharedPreferences можно использовать [encrypted-shared-prefs](/encrypted-shared-prefs)
|
||||
### Утилиты и extension функции
|
||||
В Roboswag также есть много [утилитарных](/utils) классов и [extension](/kotlin-extensions) функций, которые позволяют писать часто используемый код в одну строку.
|
||||
|
||||
|
|
@ -62,7 +62,8 @@ gradle.ext.roboswag = [
|
|||
'tabbar-navigation',
|
||||
'base-map',
|
||||
'yandex-map',
|
||||
'google-map'
|
||||
'google-map',
|
||||
'encrypted-shared-prefs'
|
||||
]
|
||||
|
||||
gradle.ext.roboswag.forEach { module ->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
Encrypted shared preferences
|
||||
============================
|
||||
|
||||
Модуль с реализацией интерфейса `SharedPreferences`, который дает возможность шифровать содержимое.
|
||||
|
||||
### Пример
|
||||
|
||||
Пример получения экземпляра `TouchinSharedPreferences`. При encrypt = false, `TouchinSharedPreferences` абсолютно аналогичны стандартной реализации `SharedPreferences`
|
||||
|
||||
```kotlin
|
||||
val prefs = TouchinSharedPreferences(name = "APPLICATION_DATA_ENCRYPTED", context = context, encrypt = true)
|
||||
```
|
||||
|
||||
Важно помнить, что в одном файле `TouchinSharedPreferences` могут храниться только либо полностью зашифрованные данные, либо полностью незашифрованные. Флаг `isEncryption` должен быть в соответствующем положении
|
||||
|
|
@ -2,11 +2,13 @@ package ru.touchin.roboswag
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import ru.touchin.roboswag.PrefsCryptoUtils.Companion.ENCRYPT_BASE64_STRING_LENGTH
|
||||
import ru.touchin.roboswag.PrefsCryptoUtils.Companion.ENCRYPT_BLOCK_SIZE
|
||||
|
||||
class TouchinSharedPreferences(name: String, context: Context, val isEncryption: Boolean = false) : SharedPreferences {
|
||||
class CipherSharedPreferences(name: String, context: Context, val encrypt: Boolean = false) : SharedPreferences {
|
||||
|
||||
private val currentPreferences: SharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE)
|
||||
private val cryptoUtils = TouchinSharedPreferencesCryptoUtils(context)
|
||||
private val cryptoUtils = PrefsCryptoUtils(context)
|
||||
|
||||
override fun contains(key: String?) = currentPreferences.contains(key)
|
||||
|
||||
|
|
@ -19,7 +21,7 @@ class TouchinSharedPreferences(name: String, context: Context, val isEncryption:
|
|||
override fun getInt(key: String?, defaultValue: Int) = get(key, defaultValue)
|
||||
|
||||
override fun getAll(): MutableMap<String, String> {
|
||||
return if (isEncryption) {
|
||||
return if (encrypt) {
|
||||
currentPreferences.all.mapValues { it.value.toString().decrypt() }.toMutableMap()
|
||||
} else {
|
||||
currentPreferences.all.mapValues { it.value.toString() }.toMutableMap()
|
||||
|
|
@ -35,7 +37,7 @@ class TouchinSharedPreferences(name: String, context: Context, val isEncryption:
|
|||
override fun getString(key: String?, defaultValue: String?): String = get(key, defaultValue ?: "")
|
||||
|
||||
override fun getStringSet(key: String?, set: MutableSet<String>?): MutableSet<String>? {
|
||||
return if (isEncryption) {
|
||||
return if (encrypt) {
|
||||
val value = currentPreferences.getStringSet(key, set)
|
||||
if (value == set) {
|
||||
set
|
||||
|
|
@ -53,18 +55,22 @@ class TouchinSharedPreferences(name: String, context: Context, val isEncryption:
|
|||
|
||||
private fun <T> get(key: String?, defaultValue: T): T {
|
||||
if (!currentPreferences.contains(key)) return defaultValue
|
||||
val value = currentPreferences.getString(key, "")?.decrypt()
|
||||
val resultValue = currentPreferences.getString(key, "")
|
||||
?.trim()
|
||||
?.chunked(ENCRYPT_BASE64_STRING_LENGTH)
|
||||
?.joinToString(separator = "", transform = { it.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
|
||||
is Boolean -> resultValue?.toBoolean() as? T
|
||||
is Long -> resultValue?.toLong() as? T
|
||||
is String -> resultValue as? T
|
||||
is Int -> resultValue?.toInt() as? T
|
||||
is Float -> resultValue?.toFloat() as? T
|
||||
else -> resultValue as? T
|
||||
} ?: defaultValue
|
||||
}
|
||||
|
||||
private fun String.decrypt() = if (isEncryption) cryptoUtils.decrypt(this) else this
|
||||
private fun String.decrypt() = if (encrypt) cryptoUtils.decrypt(this) else this
|
||||
|
||||
inner class TouchinEditor : SharedPreferences.Editor {
|
||||
|
||||
|
|
@ -79,7 +85,7 @@ class TouchinSharedPreferences(name: String, context: Context, val isEncryption:
|
|||
override fun putBoolean(key: String?, value: Boolean) = put(key, value)
|
||||
|
||||
override fun putStringSet(key: String?, value: MutableSet<String>?): SharedPreferences.Editor {
|
||||
return if (isEncryption) {
|
||||
return if (encrypt) {
|
||||
currentPreferences.edit().putStringSet(key, value?.map { it.encrypt() }?.toMutableSet())
|
||||
} else {
|
||||
currentPreferences.edit().putStringSet(key, value)
|
||||
|
|
@ -94,9 +100,13 @@ class TouchinSharedPreferences(name: String, context: Context, val isEncryption:
|
|||
|
||||
override fun putString(key: String?, value: String?) = put(key, value)
|
||||
|
||||
private fun <T> put(key: String?, value: T) = currentPreferences.edit().putString(key, value.toString().encrypt())
|
||||
private fun <T> put(key: String?, value: T): SharedPreferences.Editor {
|
||||
val resultValue = value?.toString()?.chunked(ENCRYPT_BLOCK_SIZE)?.joinToString(separator = "", transform = { it.encrypt() })
|
||||
|
||||
private fun String.encrypt() = if (isEncryption) cryptoUtils.encrypt(this) else this
|
||||
return currentPreferences.edit().putString(key, resultValue)
|
||||
}
|
||||
|
||||
private fun String.encrypt() = if (encrypt) cryptoUtils.encrypt(this) else this
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -2,7 +2,7 @@ package ru.touchin.roboswag
|
|||
|
||||
import android.content.SharedPreferences
|
||||
|
||||
fun TouchinSharedPreferences.migrateFromSharedPreferences(from: SharedPreferences, key: String): SharedPreferences {
|
||||
fun CipherSharedPreferences.migrateFromSharedPreferences(from: SharedPreferences, key: String): SharedPreferences {
|
||||
if (!from.contains(key)) return this
|
||||
edit().putString(key, from.getString(key, "") ?: "").apply()
|
||||
from.edit().remove(key).apply()
|
||||
|
|
|
|||
|
|
@ -18,14 +18,24 @@ 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) {
|
||||
class PrefsCryptoUtils 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 const val CIPHER_STRING_SIZE_BYTES = 256
|
||||
private const val BASE_64_PADDING = 2
|
||||
private const val STORAGE_KEY = "STORAGE_KEY"
|
||||
|
||||
//https://stackoverflow.com/questions/13378815/base64-length-calculation
|
||||
private const val DECRYPTED_BYTES_COUNT = 3
|
||||
private const val ENCRYPTED_BYTES_COUNT = 4
|
||||
private const val BASE64_DIVIDER_COUNT = 5
|
||||
const val ENCRYPT_BASE64_STRING_LENGTH =
|
||||
(CIPHER_STRING_SIZE_BYTES + BASE_64_PADDING) * ENCRYPTED_BYTES_COUNT / DECRYPTED_BYTES_COUNT + BASE64_DIVIDER_COUNT
|
||||
const val ENCRYPT_BLOCK_SIZE = 128
|
||||
|
||||
private fun getAndroidKeystore(): KeyStore? = try {
|
||||
KeyStore.getInstance(ANDROID_KEY_STORE).also { it.load(null) }
|
||||
Loading…
Reference in New Issue