Merge pull request #134 from TouchInstinct/enc-shared-prefs

Enc shared prefs
This commit is contained in:
Aksenov Vladimir 2020-06-15 15:03:31 +03:00 committed by GitHub
commit 696d358d29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 55 additions and 20 deletions

View File

@ -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 ->

View File

@ -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` должен быть в соответствующем положении

View File

@ -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
}

View File

@ -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()

View File

@ -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) }