Это мой открытый класс, который я использую для реализации различных хранилищ данных для разных типов. данных
Код: Выделить всё
open class SecureDataStore (
private val context: Context,
private val name: String,
) {
private val secureDataStore = PreferenceDataStoreFactory.create(
corruptionHandler = ReplaceFileCorruptionHandler(
produceNewData = { emptyPreferences() }
),
produceFile = { context.preferencesDataStoreFile(name) }
)
suspend fun clearDataStore() {
secureDataStore.edit {
it.clear()
}
}
private val securityKeyAlias = Config.SecureDatastore.SECURE_KEY_ALIAS
private val bytesToStringSeparator = "|"
private val provider = Config.SecureDatastore.PROVIDER
private val charset by lazy {
charset("UTF-8")
}
private val keyStore by lazy {
KeyStore.getInstance(provider).apply {
load(null)
}
}
private val keyGenerator by lazy {
KeyGenerator.getInstance(KEY_ALGORITHM_AES, provider)
}
private fun getCipherInstance(): Cipher {
return Cipher.getInstance(Config.SecureDatastore.CIPHER_INSTANCE)
}
private fun generateSecretKey(): SecretKey {
return keyGenerator.apply {
init(
KeyGenParameterSpec
.Builder(securityKeyAlias, PURPOSE_ENCRYPT or PURPOSE_DECRYPT)
.setBlockModes(BLOCK_MODE_GCM)
.setEncryptionPaddings(ENCRYPTION_PADDING_NONE)
.build()
)
}.generateKey()
}
private fun getSecretKey() =
(keyStore.getEntry(securityKeyAlias, null) as KeyStore.SecretKeyEntry).secretKey
private fun encryptData(text: String): ByteArray {
val cipher = getCipherInstance() // Create a new cipher instance
cipher.init(Cipher.ENCRYPT_MODE, generateSecretKey())
val iv = cipher.iv
val encryptedBytes = cipher.doFinal(text.toByteArray(charset))
return iv + encryptedBytes
}
private fun decryptData(encryptedData: ByteArray): String {
val ivSize = 12 // Standard GCM IV size
val iv = encryptedData.sliceArray(0 until ivSize)
val encryptedBytes = encryptedData.sliceArray(ivSize until encryptedData.size)
val cipher = getCipherInstance() // Create a new cipher instance
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), GCMParameterSpec(128, iv))
return cipher.doFinal(encryptedBytes).toString(charset)
}
private fun getEncryptedValueFlow(key: String): Flow {
val preferencesKey = stringPreferencesKey(key)
return secureDataStore.data.map { preferences ->
preferences[preferencesKey]?.split(bytesToStringSeparator)?.map { it.toByte() }?.toByteArray()
}
}
private suspend fun getEncryptedValue(key: String): ByteArray? {
return getEncryptedValueFlow(key).firstOrNull()
}
/**
* Saves encrypted data of type [T]
* @param key StringPreferencesKey
* @param value data (Serializable Data class)
* @param serializer KSerializer class to serialize passed data class
*/
suspend fun saveEncryptedData(key: String, value: T, serializer: KSerializer) {
secureDataStore.edit {
val jsonString = Json.encodeToString(serializer, value) // Serialize the data class
val encryptedValue = encryptData(jsonString) // Encrypt the serialized string
it[stringPreferencesKey(key)] = encryptedValue.joinToString(bytesToStringSeparator) // Save encrypted string
}
}
/**
* Gets decrypted data of type [T]
* @param key StringPreferencesKey
* @param serializer KSerializer class to deserialize data class
* @return data class [T]
*/
suspend fun getDecryptedData(key: String, serializer: KSerializer): T? {
val encryptedValue = getEncryptedValue(key)
return encryptedValue?.let {
val decryptedJson = decryptData(it) // Decrypt the data to JSON string
Json.decodeFromString(serializer, decryptedJson) // Deserialize JSON back into the data class
}
}
suspend fun saveEncryptedBoolean(key: String, value: Boolean) {
secureDataStore.edit {
val encryptedValue = encryptData(value.toString()) // Convert Boolean to String and encrypt
it[stringPreferencesKey(key)] = encryptedValue.joinToString(bytesToStringSeparator)
}
}
suspend fun getDecryptedBoolean(key: String): Boolean? {
val encryptedValue = getEncryptedValue(key)
return encryptedValue?.let {
val decryptedString = decryptData(it) // Decrypt the data to String
decryptedString.toBoolean() // Convert String back to Boolean
}
}
suspend fun saveEncryptedString(key: String, value: String) {
Timber.d("SecureDataStore: saveEncryptedString: key: $key, value: $value")
secureDataStore.edit {
val encryptedValue = encryptData(value)
it[stringPreferencesKey(key)] = encryptedValue.joinToString(bytesToStringSeparator)
}
}
suspend fun getDecryptedString(key: String): String? {
val encryptedValue = getEncryptedValue(key)
Timber.d("SecureDataStore: getDecryptedString: key: $key, encryptedValue: ${encryptedValue.contentToString()}")
val decryptedValue = encryptedValue?.let { decryptData(it) }
Timber.d("SecureDataStore: getDecryptedString: key: $key, decryptedValue: ${decryptedValue?:"null"}")
return decryptedValue
}
fun SecureDataStore.getDecryptedFlowString(key: String): Flow {
return getEncryptedValueFlow(key).map { encryptedData ->
encryptedData?.let { decryptData(it) }
}
}
}
Код: Выделить всё
const val SECURE_KEY_ALIAS = "data-store" //change after testing
const val PROVIDER = "AndroidKeyStore"
const val CIPHER_INSTANCE = "AES/GCM/NoPadding"
Код: Выделить всё
saveEncryptedString/getDecryptedString
Подробнее здесь: https://stackoverflow.com/questions/792 ... pty-string
Мобильная версия