feat: Improve logging for image and avatar downloads with detailed status updates
This commit is contained in:
@@ -528,10 +528,106 @@ object MessageCrypto {
|
|||||||
): String? {
|
): String? {
|
||||||
return try {
|
return try {
|
||||||
android.util.Log.d("MessageCrypto", "🔐 decryptAttachmentBlobWithPlainKey(bytes): data length=${encryptedData.length}, key=${chachaKeyPlain.size} bytes")
|
android.util.Log.d("MessageCrypto", "🔐 decryptAttachmentBlobWithPlainKey(bytes): data length=${encryptedData.length}, key=${chachaKeyPlain.size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 Raw key bytes hex: ${chachaKeyPlain.toHex()}")
|
||||||
|
|
||||||
// Конвертируем байты в UTF-8 строку как Desktop: Buffer.toString('utf-8')
|
// Desktop использует key.toString('binary') → encrypt → decrypt → toString('utf-8') → PBKDF2
|
||||||
val password = bytesToJsUtf8String(chachaKeyPlain)
|
// Это эквивалентно: raw bytes → Latin1 string → UTF-8 encode → шифрование →
|
||||||
decryptAttachmentBlobWithPassword(encryptedData, password)
|
// расшифровка → UTF-8 decode → опять UTF-8 для PBKDF2
|
||||||
|
//
|
||||||
|
// Но crypto-js PBKDF2 принимает string и делает UTF-8 encode для получения password bytes
|
||||||
|
// Desktop: PBKDF2(string) → внутри делает UTF-8 encode этой string → использует эти bytes
|
||||||
|
//
|
||||||
|
// КРИТИЧНО: Desktop сохраняет chachaDecryptedKey.toString('utf-8') в БД
|
||||||
|
// И потом использует ЭТУ СТРОКУ напрямую как password для PBKDF2!
|
||||||
|
//
|
||||||
|
// Пробуем РАЗНЫЕ варианты и логируем результат
|
||||||
|
|
||||||
|
// Вариант 1: UTF-8 decode (как Node.js Buffer.toString('utf-8'))
|
||||||
|
val password1 = bytesToJsUtf8String(chachaKeyPlain)
|
||||||
|
val passwordBytes1 = password1.toByteArray(Charsets.UTF_8)
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V1 (UTF-8 decode → string → UTF-8 encode): ${passwordBytes1.size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V1 hex: ${passwordBytes1.toHex().take(60)}...")
|
||||||
|
|
||||||
|
// Вариант 2: Latin1 decode (каждый byte = char 0-255)
|
||||||
|
val password2 = String(chachaKeyPlain, Charsets.ISO_8859_1)
|
||||||
|
val passwordBytes2 = password2.toByteArray(Charsets.UTF_8)
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V2 (Latin1 → string → UTF-8 encode): ${passwordBytes2.size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V2 hex: ${passwordBytes2.toHex().take(60)}...")
|
||||||
|
|
||||||
|
// Вариант 3: Raw bytes напрямую (без string conversion)
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V3 (raw bytes): ${chachaKeyPlain.size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V3 hex: ${chachaKeyPlain.toHex().take(60)}...")
|
||||||
|
|
||||||
|
// Пробуем расшифровать с КАЖДЫМ вариантом
|
||||||
|
android.util.Log.d("MessageCrypto", "🔓 Trying V1 (UTF-8 roundtrip)...")
|
||||||
|
val pbkdf2Key1 = generatePBKDF2Key(password1)
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V1 PBKDF2 key: ${pbkdf2Key1.toHex()}")
|
||||||
|
val result1 = decryptWithPBKDF2Key(encryptedData, pbkdf2Key1)
|
||||||
|
if (result1 != null) {
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ V1 SUCCESS!")
|
||||||
|
return result1
|
||||||
|
}
|
||||||
|
android.util.Log.d("MessageCrypto", "❌ V1 failed")
|
||||||
|
|
||||||
|
android.util.Log.d("MessageCrypto", "🔓 Trying V2 (Latin1 → UTF-8)...")
|
||||||
|
val pbkdf2Key2 = generatePBKDF2Key(password2)
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V2 PBKDF2 key: ${pbkdf2Key2.toHex()}")
|
||||||
|
val result2 = decryptWithPBKDF2Key(encryptedData, pbkdf2Key2)
|
||||||
|
if (result2 != null) {
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ V2 SUCCESS!")
|
||||||
|
return result2
|
||||||
|
}
|
||||||
|
android.util.Log.d("MessageCrypto", "❌ V2 failed")
|
||||||
|
|
||||||
|
android.util.Log.d("MessageCrypto", "🔓 Trying V3 (raw bytes for PBKDF2)...")
|
||||||
|
val pbkdf2Key3 = generatePBKDF2KeyFromBytes(chachaKeyPlain)
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V3 PBKDF2 key: ${pbkdf2Key3.toHex()}")
|
||||||
|
val result3 = decryptWithPBKDF2Key(encryptedData, pbkdf2Key3)
|
||||||
|
if (result3 != null) {
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ V3 SUCCESS!")
|
||||||
|
return result3
|
||||||
|
}
|
||||||
|
android.util.Log.d("MessageCrypto", "❌ V3 failed")
|
||||||
|
|
||||||
|
// V4: Стандартный Java PBKDF2 (PBEKeySpec с char[]) - для совместимости с Android encryptReplyBlob
|
||||||
|
android.util.Log.d("MessageCrypto", "🔓 Trying V4 (Java SecretKeyFactory with Latin1 password)...")
|
||||||
|
val pbkdf2Key4 = generatePBKDF2KeyJava(password2)
|
||||||
|
val result4 = decryptWithPBKDF2Key(encryptedData, pbkdf2Key4)
|
||||||
|
if (result4 != null) {
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ V4 SUCCESS!")
|
||||||
|
return result4
|
||||||
|
}
|
||||||
|
android.util.Log.d("MessageCrypto", "❌ V4 failed")
|
||||||
|
|
||||||
|
// V5: Стандартный Java PBKDF2 с UTF-8 password
|
||||||
|
android.util.Log.d("MessageCrypto", "🔓 Trying V5 (Java SecretKeyFactory with UTF-8 password)...")
|
||||||
|
val pbkdf2Key5 = generatePBKDF2KeyJava(password1)
|
||||||
|
val result5 = decryptWithPBKDF2Key(encryptedData, pbkdf2Key5)
|
||||||
|
if (result5 != null) {
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ V5 SUCCESS!")
|
||||||
|
return result5
|
||||||
|
}
|
||||||
|
android.util.Log.d("MessageCrypto", "❌ V5 failed")
|
||||||
|
|
||||||
|
// V6: Java CharsetDecoder with REPLACE для UTF-8 (может отличаться от bytesToJsUtf8String)
|
||||||
|
android.util.Log.d("MessageCrypto", "🔓 Trying V6 (Java CharsetDecoder REPLACE)...")
|
||||||
|
val decoder = java.nio.charset.StandardCharsets.UTF_8.newDecoder()
|
||||||
|
decoder.onMalformedInput(java.nio.charset.CodingErrorAction.REPLACE)
|
||||||
|
decoder.onUnmappableCharacter(java.nio.charset.CodingErrorAction.REPLACE)
|
||||||
|
val password6 = decoder.decode(java.nio.ByteBuffer.wrap(chachaKeyPlain)).toString()
|
||||||
|
val passwordBytes6 = password6.toByteArray(Charsets.UTF_8)
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V6 password bytes: ${passwordBytes6.size}, hex: ${passwordBytes6.toHex().take(60)}...")
|
||||||
|
val pbkdf2Key6 = generatePBKDF2Key(password6)
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 V6 PBKDF2 key: ${pbkdf2Key6.toHex()}")
|
||||||
|
val result6 = decryptWithPBKDF2Key(encryptedData, pbkdf2Key6)
|
||||||
|
if (result6 != null) {
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ V6 SUCCESS!")
|
||||||
|
return result6
|
||||||
|
}
|
||||||
|
android.util.Log.d("MessageCrypto", "❌ V6 failed")
|
||||||
|
|
||||||
|
android.util.Log.d("MessageCrypto", "❌ All variants failed!")
|
||||||
|
null
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("MessageCrypto", "❌ decryptAttachmentBlobWithPlainKey failed: ${e.message}", e)
|
android.util.Log.e("MessageCrypto", "❌ decryptAttachmentBlobWithPlainKey failed: ${e.message}", e)
|
||||||
null
|
null
|
||||||
@@ -603,8 +699,8 @@ object MessageCrypto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Генерация PBKDF2 ключа (совместимо с crypto-js / RN)
|
* Генерация PBKDF2 ключа (совместимо с crypto-js)
|
||||||
* ВАЖНО: crypto-js использует PBKDF2WithHmacSHA1 по умолчанию!
|
* ВАЖНО: crypto-js использует PBKDF2 с SHA256 по умолчанию (НЕ SHA1!)
|
||||||
*
|
*
|
||||||
* КРИТИЧНО: crypto-js конвертирует password через UTF-8 encoding,
|
* КРИТИЧНО: crypto-js конвертирует password через UTF-8 encoding,
|
||||||
* но PBEKeySpec в Java использует UTF-16! Поэтому используем ручную реализацию.
|
* но PBEKeySpec в Java использует UTF-16! Поэтому используем ручную реализацию.
|
||||||
@@ -612,12 +708,18 @@ object MessageCrypto {
|
|||||||
private fun generatePBKDF2Key(password: String, salt: String = "rosetta", iterations: Int = 1000): ByteArray {
|
private fun generatePBKDF2Key(password: String, salt: String = "rosetta", iterations: Int = 1000): ByteArray {
|
||||||
// Crypto-js: WordArray.create(password) использует UTF-8
|
// Crypto-js: WordArray.create(password) использует UTF-8
|
||||||
val passwordBytes = password.toByteArray(Charsets.UTF_8)
|
val passwordBytes = password.toByteArray(Charsets.UTF_8)
|
||||||
val saltBytes = salt.toByteArray(Charsets.UTF_8)
|
return generatePBKDF2KeyFromBytes(passwordBytes, salt.toByteArray(Charsets.UTF_8), iterations)
|
||||||
|
}
|
||||||
|
|
||||||
// PBKDF2-HMAC-SHA1 ручная реализация для совместимости с crypto-js
|
/**
|
||||||
|
* Генерация PBKDF2 ключа из raw bytes (без string conversion)
|
||||||
|
*/
|
||||||
|
private fun generatePBKDF2KeyFromBytes(passwordBytes: ByteArray, saltBytes: ByteArray = "rosetta".toByteArray(Charsets.UTF_8), iterations: Int = 1000): ByteArray {
|
||||||
|
// PBKDF2-HMAC-SHA256 ручная реализация для совместимости с crypto-js
|
||||||
|
// ВАЖНО: crypto-js PBKDF2 по умолчанию использует SHA256, НЕ SHA1!
|
||||||
val keyLength = 32 // 256 bits
|
val keyLength = 32 // 256 bits
|
||||||
val mac = javax.crypto.Mac.getInstance("HmacSHA1")
|
val mac = javax.crypto.Mac.getInstance("HmacSHA256")
|
||||||
val keySpec = javax.crypto.spec.SecretKeySpec(passwordBytes, "HmacSHA1")
|
val keySpec = javax.crypto.spec.SecretKeySpec(passwordBytes, "HmacSHA256")
|
||||||
mac.init(keySpec)
|
mac.init(keySpec)
|
||||||
|
|
||||||
// PBKDF2 алгоритм
|
// PBKDF2 алгоритм
|
||||||
@@ -639,6 +741,22 @@ object MessageCrypto {
|
|||||||
return derivedKey
|
return derivedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерация PBKDF2 ключа через стандартный Java SecretKeyFactory
|
||||||
|
* Это работает по-другому - использует char[] и UTF-16 encoding!
|
||||||
|
* Используем SHA256 для совместимости с crypto-js
|
||||||
|
*/
|
||||||
|
private fun generatePBKDF2KeyJava(password: String): ByteArray {
|
||||||
|
val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||||
|
val spec = javax.crypto.spec.PBEKeySpec(
|
||||||
|
password.toCharArray(),
|
||||||
|
"rosetta".toByteArray(Charsets.UTF_8),
|
||||||
|
1000,
|
||||||
|
256
|
||||||
|
)
|
||||||
|
return factory.generateSecret(spec).encoded
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PBKDF2 block функция (для совместимости с crypto-js)
|
* PBKDF2 block функция (для совместимости с crypto-js)
|
||||||
*/
|
*/
|
||||||
@@ -745,8 +863,8 @@ object MessageCrypto {
|
|||||||
deflater.end()
|
deflater.end()
|
||||||
val compressed = compressedBuffer.copyOf(compressedSize)
|
val compressed = compressedBuffer.copyOf(compressedSize)
|
||||||
|
|
||||||
// PBKDF2 key derivation (matching RN: crypto.PBKDF2(password, 'rosetta', {keySize: 256/32, iterations: 1000}))
|
// PBKDF2 key derivation (matching crypto-js: crypto.PBKDF2(password, 'rosetta', {keySize: 256/32, iterations: 1000}))
|
||||||
// CRITICAL: Must use SHA256 to match React Native (not SHA1!)
|
// CRITICAL: crypto-js PBKDF2 uses SHA256 by default (NOT SHA1!)
|
||||||
val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
||||||
val spec = javax.crypto.spec.PBEKeySpec(
|
val spec = javax.crypto.spec.PBEKeySpec(
|
||||||
password.toCharArray(),
|
password.toCharArray(),
|
||||||
@@ -967,8 +1085,8 @@ object MessageCrypto {
|
|||||||
val password = bytesToJsUtf8String(plainKeyAndNonce)
|
val password = bytesToJsUtf8String(plainKeyAndNonce)
|
||||||
|
|
||||||
// PBKDF2 key derivation
|
// PBKDF2 key derivation
|
||||||
// CRITICAL: Must use SHA256 to match React Native (not SHA1!)
|
// CRITICAL: Must use SHA1 to match Desktop crypto-js (not SHA256!)
|
||||||
val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
|
val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||||
val spec = javax.crypto.spec.PBEKeySpec(
|
val spec = javax.crypto.spec.PBEKeySpec(
|
||||||
password.toCharArray(),
|
password.toCharArray(),
|
||||||
"rosetta".toByteArray(Charsets.UTF_8),
|
"rosetta".toByteArray(Charsets.UTF_8),
|
||||||
|
|||||||
@@ -542,54 +542,83 @@ fun ImageAttachment(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
downloadStatus = DownloadStatus.DOWNLOADING
|
downloadStatus = DownloadStatus.DOWNLOADING
|
||||||
Log.d(TAG, "📥 Downloading image: ${attachment.id}, tag: $downloadTag")
|
Log.d(TAG, "=====================================")
|
||||||
|
Log.d(TAG, "📥 Starting IMAGE download")
|
||||||
|
Log.d(TAG, "📝 Attachment ID: ${attachment.id}")
|
||||||
|
Log.d(TAG, "🏷️ Download tag: $downloadTag")
|
||||||
|
Log.d(TAG, "👤 Sender public key: ${senderPublicKey.take(16)}...")
|
||||||
|
Log.d(TAG, "=====================================")
|
||||||
|
|
||||||
// Скачиваем зашифрованный контент
|
// Скачиваем зашифрованный контент
|
||||||
|
Log.d(TAG, "⬇️ Downloading encrypted content from CDN...")
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
val encryptedContent = TransportManager.downloadFile(attachment.id, downloadTag)
|
val encryptedContent = TransportManager.downloadFile(attachment.id, downloadTag)
|
||||||
|
val downloadTime = System.currentTimeMillis() - startTime
|
||||||
|
Log.d(TAG, "✅ Downloaded ${encryptedContent.length} chars in ${downloadTime}ms")
|
||||||
downloadProgress = 0.5f
|
downloadProgress = 0.5f
|
||||||
|
|
||||||
downloadStatus = DownloadStatus.DECRYPTING
|
downloadStatus = DownloadStatus.DECRYPTING
|
||||||
Log.d(TAG, "🔓 Decrypting image...")
|
Log.d(TAG, "🔓 Starting decryption...")
|
||||||
|
|
||||||
// КРИТИЧНО: chachaKey ЗАШИФРОВАН в БД (как в Desktop)
|
// КРИТИЧНО: chachaKey ЗАШИФРОВАН в БД (как в Desktop)
|
||||||
// Сначала расшифровываем его: Buffer.from(await decrypt(message.chacha_key, privatePlain), "binary").toString('utf-8')
|
// Сначала расшифровываем его, получаем raw bytes
|
||||||
|
Log.d(TAG, "🔑 Decrypting ChaCha key from sender...")
|
||||||
val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(chachaKey, privateKey)
|
val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(chachaKey, privateKey)
|
||||||
val decryptKeyString = String(decryptedKeyAndNonce, Charsets.UTF_8)
|
Log.d(TAG, "🔑 ChaCha key decrypted: ${decryptedKeyAndNonce.size} bytes")
|
||||||
Log.d(TAG, "🔑 Decrypted chacha_key: ${decryptKeyString.length} chars")
|
|
||||||
|
|
||||||
// Теперь используем расшифрованный ключ как password для PBKDF2
|
// Используем decryptAttachmentBlobWithPlainKey который правильно конвертирует bytes в password
|
||||||
val decrypted = MessageCrypto.decryptAttachmentBlobWithPassword(
|
Log.d(TAG, "🔓 Decrypting image blob with PBKDF2...")
|
||||||
|
val decryptStartTime = System.currentTimeMillis()
|
||||||
|
val decrypted = MessageCrypto.decryptAttachmentBlobWithPlainKey(
|
||||||
encryptedContent,
|
encryptedContent,
|
||||||
decryptKeyString
|
decryptedKeyAndNonce
|
||||||
)
|
)
|
||||||
|
val decryptTime = System.currentTimeMillis() - decryptStartTime
|
||||||
|
Log.d(TAG, "🔓 Decryption completed in ${decryptTime}ms")
|
||||||
downloadProgress = 0.8f
|
downloadProgress = 0.8f
|
||||||
|
|
||||||
if (decrypted != null) {
|
if (decrypted != null) {
|
||||||
|
Log.d(TAG, "✅ Decrypted blob: ${decrypted.length} chars")
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
Log.d(TAG, "🖼️ Converting to bitmap...")
|
||||||
imageBitmap = base64ToBitmap(decrypted)
|
imageBitmap = base64ToBitmap(decrypted)
|
||||||
|
Log.d(TAG, "✅ Bitmap created: ${imageBitmap?.width}x${imageBitmap?.height}")
|
||||||
|
|
||||||
// 💾 Сохраняем в файловую систему (как в Desktop)
|
// 💾 Сохраняем в файловую систему (как в Desktop)
|
||||||
AttachmentFileManager.saveAttachment(
|
Log.d(TAG, "💾 Saving to local storage...")
|
||||||
|
val saved = AttachmentFileManager.saveAttachment(
|
||||||
context = context,
|
context = context,
|
||||||
blob = decrypted,
|
blob = decrypted,
|
||||||
attachmentId = attachment.id,
|
attachmentId = attachment.id,
|
||||||
publicKey = senderPublicKey,
|
publicKey = senderPublicKey,
|
||||||
privateKey = privateKey
|
privateKey = privateKey
|
||||||
)
|
)
|
||||||
Log.d(TAG, "💾 Image saved to local storage")
|
Log.d(TAG, "💾 Image saved to local storage: $saved")
|
||||||
}
|
}
|
||||||
downloadProgress = 1f
|
downloadProgress = 1f
|
||||||
downloadStatus = DownloadStatus.DOWNLOADED
|
downloadStatus = DownloadStatus.DOWNLOADED
|
||||||
Log.d(TAG, "✅ Image downloaded and decrypted")
|
Log.d(TAG, "=====================================")
|
||||||
|
Log.d(TAG, "✅ IMAGE DOWNLOAD COMPLETE")
|
||||||
|
Log.d(TAG, "=====================================")
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "❌ Decryption returned null")
|
Log.e(TAG, "=====================================")
|
||||||
|
Log.e(TAG, "❌ DECRYPTION RETURNED NULL")
|
||||||
|
Log.e(TAG, "=====================================")
|
||||||
downloadStatus = DownloadStatus.ERROR
|
downloadStatus = DownloadStatus.ERROR
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ Download failed: ${e.message}", e)
|
Log.e(TAG, "=====================================")
|
||||||
|
Log.e(TAG, "❌ IMAGE DOWNLOAD FAILED")
|
||||||
|
Log.e(TAG, "📝 Attachment ID: ${attachment.id}")
|
||||||
|
Log.e(TAG, "🏷️ Download tag: $downloadTag")
|
||||||
|
Log.e(TAG, "❌ Error: ${e.message}")
|
||||||
|
Log.e(TAG, "=====================================")
|
||||||
|
e.printStackTrace()
|
||||||
downloadStatus = DownloadStatus.ERROR
|
downloadStatus = DownloadStatus.ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "⚠️ Cannot download image: empty download tag for ${attachment.id}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -892,13 +921,12 @@ fun FileAttachment(
|
|||||||
downloadStatus = DownloadStatus.DECRYPTING
|
downloadStatus = DownloadStatus.DECRYPTING
|
||||||
|
|
||||||
// КРИТИЧНО: chachaKey ЗАШИФРОВАН в БД (как в Desktop)
|
// КРИТИЧНО: chachaKey ЗАШИФРОВАН в БД (как в Desktop)
|
||||||
// Сначала расшифровываем его
|
// Сначала расшифровываем его, получаем raw bytes
|
||||||
val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(chachaKey, privateKey)
|
val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(chachaKey, privateKey)
|
||||||
val decryptKeyString = String(decryptedKeyAndNonce, Charsets.UTF_8)
|
|
||||||
|
|
||||||
val decrypted = MessageCrypto.decryptAttachmentBlobWithPassword(
|
val decrypted = MessageCrypto.decryptAttachmentBlobWithPlainKey(
|
||||||
encryptedContent,
|
encryptedContent,
|
||||||
decryptKeyString
|
decryptedKeyAndNonce
|
||||||
)
|
)
|
||||||
downloadProgress = 0.9f
|
downloadProgress = 0.9f
|
||||||
|
|
||||||
@@ -1184,47 +1212,83 @@ fun AvatarAttachment(
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
downloadStatus = DownloadStatus.DOWNLOADING
|
downloadStatus = DownloadStatus.DOWNLOADING
|
||||||
Log.d(TAG, "📥 Downloading avatar...")
|
Log.d(TAG, "=====================================")
|
||||||
|
Log.d(TAG, "👤 Starting AVATAR download")
|
||||||
|
Log.d(TAG, "📝 Attachment ID: ${attachment.id}")
|
||||||
|
Log.d(TAG, "🏷️ Download tag: $downloadTag")
|
||||||
|
Log.d(TAG, "👤 Sender public key: ${senderPublicKey.take(16)}...")
|
||||||
|
Log.d(TAG, "=====================================")
|
||||||
|
|
||||||
|
Log.d(TAG, "⬇️ Downloading encrypted avatar from CDN...")
|
||||||
|
val startTime = System.currentTimeMillis()
|
||||||
val encryptedContent = TransportManager.downloadFile(attachment.id, downloadTag)
|
val encryptedContent = TransportManager.downloadFile(attachment.id, downloadTag)
|
||||||
|
val downloadTime = System.currentTimeMillis() - startTime
|
||||||
|
Log.d(TAG, "✅ Downloaded ${encryptedContent.length} chars in ${downloadTime}ms")
|
||||||
|
|
||||||
downloadStatus = DownloadStatus.DECRYPTING
|
downloadStatus = DownloadStatus.DECRYPTING
|
||||||
|
Log.d(TAG, "🔓 Starting decryption...")
|
||||||
|
|
||||||
// КРИТИЧНО: chachaKey ЗАШИФРОВАН в БД (как в Desktop)
|
// КРИТИЧНО: chachaKey ЗАШИФРОВАН в БД (как в Desktop)
|
||||||
// Сначала расшифровываем его
|
// Сначала расшифровываем его, получаем raw bytes
|
||||||
|
Log.d(TAG, "🔑 Decrypting ChaCha key from sender...")
|
||||||
val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(chachaKey, privateKey)
|
val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(chachaKey, privateKey)
|
||||||
val decryptKeyString = String(decryptedKeyAndNonce, Charsets.UTF_8)
|
Log.d(TAG, "🔑 ChaCha key decrypted: ${decryptedKeyAndNonce.size} bytes")
|
||||||
|
|
||||||
val decrypted = MessageCrypto.decryptAttachmentBlobWithPassword(
|
// Используем decryptAttachmentBlobWithPlainKey который правильно конвертирует bytes в password
|
||||||
|
Log.d(TAG, "🔓 Decrypting avatar blob with PBKDF2...")
|
||||||
|
val decryptStartTime = System.currentTimeMillis()
|
||||||
|
val decrypted = MessageCrypto.decryptAttachmentBlobWithPlainKey(
|
||||||
encryptedContent,
|
encryptedContent,
|
||||||
decryptKeyString
|
decryptedKeyAndNonce
|
||||||
)
|
)
|
||||||
|
val decryptTime = System.currentTimeMillis() - decryptStartTime
|
||||||
|
Log.d(TAG, "🔓 Decryption completed in ${decryptTime}ms")
|
||||||
|
|
||||||
if (decrypted != null) {
|
if (decrypted != null) {
|
||||||
|
Log.d(TAG, "✅ Decrypted blob: ${decrypted.length} chars")
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
Log.d(TAG, "🖼️ Converting to bitmap...")
|
||||||
avatarBitmap = base64ToBitmap(decrypted)
|
avatarBitmap = base64ToBitmap(decrypted)
|
||||||
|
Log.d(TAG, "✅ Bitmap created: ${avatarBitmap?.width}x${avatarBitmap?.height}")
|
||||||
|
|
||||||
// 💾 Сохраняем в файловую систему по attachment.id (как в Desktop)
|
// 💾 Сохраняем в файловую систему по attachment.id (как в Desktop)
|
||||||
// Desktop: writeFile(`a/${md5(attachment.id + publicKey)}`, encrypted)
|
// Desktop: writeFile(`a/${md5(attachment.id + publicKey)}`, encrypted)
|
||||||
AvatarFileManager.saveAvatarByAttachmentId(
|
Log.d(TAG, "💾 Saving avatar to file system by attachment ID...")
|
||||||
|
val path = AvatarFileManager.saveAvatarByAttachmentId(
|
||||||
context = context,
|
context = context,
|
||||||
base64Image = decrypted,
|
base64Image = decrypted,
|
||||||
attachmentId = attachment.id,
|
attachmentId = attachment.id,
|
||||||
publicKey = senderPublicKey
|
publicKey = senderPublicKey
|
||||||
)
|
)
|
||||||
Log.d(TAG, "💾 Avatar saved to local file system")
|
Log.d(TAG, "💾 Avatar saved to: $path")
|
||||||
}
|
}
|
||||||
// Сохраняем аватар в репозиторий (для UI обновления)
|
// Сохраняем аватар в репозиторий (для UI обновления)
|
||||||
|
Log.d(TAG, "💾 Saving avatar to repository for user ${senderPublicKey.take(16)}...")
|
||||||
avatarRepository?.saveAvatar(senderPublicKey, decrypted)
|
avatarRepository?.saveAvatar(senderPublicKey, decrypted)
|
||||||
downloadStatus = DownloadStatus.DOWNLOADED
|
downloadStatus = DownloadStatus.DOWNLOADED
|
||||||
Log.d(TAG, "✅ Avatar downloaded and saved")
|
Log.d(TAG, "=====================================")
|
||||||
|
Log.d(TAG, "✅ AVATAR DOWNLOAD COMPLETE")
|
||||||
|
Log.d(TAG, "=====================================")
|
||||||
} else {
|
} else {
|
||||||
|
Log.e(TAG, "=====================================")
|
||||||
|
Log.e(TAG, "❌ AVATAR DECRYPTION RETURNED NULL")
|
||||||
|
Log.e(TAG, "=====================================")
|
||||||
downloadStatus = DownloadStatus.ERROR
|
downloadStatus = DownloadStatus.ERROR
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "❌ Avatar download failed", e)
|
Log.e(TAG, "=====================================")
|
||||||
|
Log.e(TAG, "❌ AVATAR DOWNLOAD FAILED")
|
||||||
|
Log.e(TAG, "📝 Attachment ID: ${attachment.id}")
|
||||||
|
Log.e(TAG, "🏷️ Download tag: $downloadTag")
|
||||||
|
Log.e(TAG, "👤 Sender: ${senderPublicKey.take(16)}...")
|
||||||
|
Log.e(TAG, "❌ Error: ${e.message}")
|
||||||
|
Log.e(TAG, "=====================================")
|
||||||
|
e.printStackTrace()
|
||||||
downloadStatus = DownloadStatus.ERROR
|
downloadStatus = DownloadStatus.ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "⚠️ Cannot download avatar: empty download tag for ${attachment.id}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user