fix: optimize message decryption and caching in ChatsListViewModel and CryptoManager
This commit is contained in:
@@ -34,6 +34,21 @@ object CryptoManager {
|
||||
private val keyPairCache = mutableMapOf<String, KeyPairData>()
|
||||
private val privateKeyHashCache = mutableMapOf<String, String>()
|
||||
|
||||
// 🚀 КРИТИЧЕСКАЯ ОПТИМИЗАЦИЯ: Кэш для PBKDF2 производных ключей
|
||||
// PBKDF2 с 1000 итерациями - очень тяжелая операция!
|
||||
// Кэшируем производный ключ для каждого пароля чтобы не вычислять его каждый раз
|
||||
private val pbkdf2KeyCache = mutableMapOf<String, SecretKeySpec>()
|
||||
|
||||
// 🚀 ОПТИМИЗАЦИЯ: LRU-кэш для расшифрованных сообщений
|
||||
// Ключ = encryptedData, Значение = расшифрованный текст
|
||||
// Ограничиваем размер чтобы не съесть память
|
||||
private const val DECRYPTION_CACHE_SIZE = 500
|
||||
private val decryptionCache = object : LinkedHashMap<String, String>(DECRYPTION_CACHE_SIZE, 0.75f, true) {
|
||||
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, String>?): Boolean {
|
||||
return size > DECRYPTION_CACHE_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// Add BouncyCastle provider for secp256k1 support
|
||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
||||
@@ -41,6 +56,30 @@ object CryptoManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🚀 Получить или вычислить PBKDF2 ключ для пароля (кэшируется)
|
||||
*/
|
||||
private fun getPbkdf2Key(password: String): SecretKeySpec {
|
||||
return pbkdf2KeyCache.getOrPut(password) {
|
||||
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
val spec = PBEKeySpec(password.toCharArray(), SALT.toByteArray(Charsets.UTF_8), PBKDF2_ITERATIONS, KEY_SIZE)
|
||||
val secretKey = factory.generateSecret(spec)
|
||||
SecretKeySpec(secretKey.encoded, "AES")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧹 Очистить кэши при logout
|
||||
*/
|
||||
fun clearCaches() {
|
||||
pbkdf2KeyCache.clear()
|
||||
synchronized(decryptionCache) {
|
||||
decryptionCache.clear()
|
||||
}
|
||||
keyPairCache.clear()
|
||||
privateKeyHashCache.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new 12-word BIP39 seed phrase
|
||||
*/
|
||||
@@ -252,9 +291,40 @@ object CryptoManager {
|
||||
* - Supports old format (base64-encoded hex "iv:ciphertext")
|
||||
* - Supports new format (base64 "iv:ciphertext")
|
||||
* - Supports chunked format ("CHNK:" + chunks joined by "::")
|
||||
*
|
||||
* 🚀 ОПТИМИЗАЦИЯ: Кэширование PBKDF2 ключа и расшифрованных сообщений
|
||||
*/
|
||||
fun decryptWithPassword(encryptedData: String, password: String): String? {
|
||||
// 🚀 ОПТИМИЗАЦИЯ: Проверяем кэш расшифрованных сообщений
|
||||
val cacheKey = "$password:$encryptedData"
|
||||
synchronized(decryptionCache) {
|
||||
decryptionCache[cacheKey]?.let { return it }
|
||||
}
|
||||
|
||||
return try {
|
||||
val result = decryptWithPasswordInternal(encryptedData, password)
|
||||
|
||||
// 🚀 Сохраняем в кэш
|
||||
if (result != null) {
|
||||
synchronized(decryptionCache) {
|
||||
decryptionCache[cacheKey] = result
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔐 Внутренняя функция расшифровки (без кэширования результата)
|
||||
*/
|
||||
private fun decryptWithPasswordInternal(encryptedData: String, password: String): String? {
|
||||
return try {
|
||||
// 🚀 Получаем кэшированный PBKDF2 ключ
|
||||
val key = getPbkdf2Key(password)
|
||||
|
||||
// Check for old format: base64-encoded string containing hex
|
||||
if (isOldFormat(encryptedData)) {
|
||||
val decoded = String(Base64.decode(encryptedData, Base64.NO_WRAP), Charsets.UTF_8)
|
||||
@@ -264,13 +334,7 @@ object CryptoManager {
|
||||
val iv = parts[0].chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
val ciphertext = parts[1].chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
|
||||
// Derive key using PBKDF2-HMAC-SHA1
|
||||
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
val spec = PBEKeySpec(password.toCharArray(), SALT.toByteArray(Charsets.UTF_8), PBKDF2_ITERATIONS, KEY_SIZE)
|
||||
val secretKey = factory.generateSecret(spec)
|
||||
val key = SecretKeySpec(secretKey.encoded, "AES")
|
||||
|
||||
// Decrypt with AES-256-CBC
|
||||
// Decrypt with AES-256-CBC (используем кэшированный ключ!)
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
|
||||
val decrypted = cipher.doFinal(ciphertext)
|
||||
@@ -290,13 +354,7 @@ object CryptoManager {
|
||||
val iv = Base64.decode(parts[0], Base64.NO_WRAP)
|
||||
val ciphertext = Base64.decode(parts[1], Base64.NO_WRAP)
|
||||
|
||||
// Derive key using PBKDF2-HMAC-SHA1
|
||||
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
val spec = PBEKeySpec(password.toCharArray(), SALT.toByteArray(Charsets.UTF_8), PBKDF2_ITERATIONS, KEY_SIZE)
|
||||
val secretKey = factory.generateSecret(spec)
|
||||
val key = SecretKeySpec(secretKey.encoded, "AES")
|
||||
|
||||
// Decrypt with AES-256-CBC
|
||||
// Decrypt with AES-256-CBC (используем кэшированный ключ!)
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
|
||||
val decrypted = cipher.doFinal(ciphertext)
|
||||
@@ -318,13 +376,7 @@ object CryptoManager {
|
||||
val iv = Base64.decode(parts[0], Base64.NO_WRAP)
|
||||
val ciphertext = Base64.decode(parts[1], Base64.NO_WRAP)
|
||||
|
||||
// Derive key using PBKDF2-HMAC-SHA1 (⚠️ SHA1, не SHA256!)
|
||||
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
val spec = PBEKeySpec(password.toCharArray(), SALT.toByteArray(Charsets.UTF_8), PBKDF2_ITERATIONS, KEY_SIZE)
|
||||
val secretKey = factory.generateSecret(spec)
|
||||
val key = SecretKeySpec(secretKey.encoded, "AES")
|
||||
|
||||
// Decrypt with AES-256-CBC
|
||||
// Decrypt with AES-256-CBC (используем кэшированный ключ!)
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv))
|
||||
val decrypted = cipher.doFinal(ciphertext)
|
||||
|
||||
Reference in New Issue
Block a user