SecureDataStore для Android: получение расшифрованной пустой строкиAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 SecureDataStore для Android: получение расшифрованной пустой строки

Сообщение Anonymous »

Я хочу реализовать SecuredDataStore, чтобы каждое сохраненное значение было зашифровано ключом, а затем, если я его прочитаю, оно будет расшифровано обратно до исходного значения. Я хочу использовать его для хранения некоторых динамических ключей API, которые используются внутри приложения и не могут быть сохранены в BuildConfig.
Это мой открытый класс, который я использую для реализации различных хранилищ данных для разных типов. данных

Код: Выделить всё

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
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»