Фикс: расшифровка групповых фото — fallback на hex group key (Desktop v1.2.1 parity)

Desktop теперь шифрует аттачменты в группах hex-версией ключа.
Android пробует raw key, при неудаче — hex key. Фикс в 3 местах:
processDownloadedImage, downloadAndDecryptImage, loadBitmapForViewerImage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 02:20:46 +05:00
parent e5ff42ce1d
commit 30327fade2
4 changed files with 39 additions and 6 deletions

View File

@@ -141,6 +141,7 @@ class MainActivity : FragmentActivity() {
// 🔥 Инициализируем ProtocolManager для обработки онлайн статусов // 🔥 Инициализируем ProtocolManager для обработки онлайн статусов
ProtocolManager.initialize(this) ProtocolManager.initialize(this)
CallManager.initialize(this) CallManager.initialize(this)
com.rosetta.messenger.ui.chats.components.AttachmentDownloadDebugLogger.init(this)
// 🔔 Инициализируем Firebase для push-уведомлений // 🔔 Инициализируем Firebase для push-уведомлений
initializeFirebase() initializeFirebase()

View File

@@ -2851,7 +2851,14 @@ private suspend fun processDownloadedImage(
var decryptDebug = MessageCrypto.AttachmentDecryptDebugResult(null, emptyList()) var decryptDebug = MessageCrypto.AttachmentDecryptDebugResult(null, emptyList())
val decrypted = val decrypted =
if (groupPassword != null) { if (groupPassword != null) {
val plain = CryptoManager.decryptWithPassword(encryptedContent, groupPassword!!) // Try raw group key first, then hex-encoded (Desktop v1.2.1+ sends hex-encrypted attachments)
var plain = CryptoManager.decryptWithPassword(encryptedContent, groupPassword!!)
if (plain == null) {
val hexKey = groupPassword!!.toByteArray(Charsets.ISO_8859_1)
.joinToString("") { "%02x".format(it.toInt() and 0xff) }
logPhotoDebug("Group raw key failed, trying hex key: id=$idShort, hexKeyLen=${hexKey.length}")
plain = CryptoManager.decryptWithPassword(encryptedContent, hexKey)
}
decryptDebug = MessageCrypto.AttachmentDecryptDebugResult(plain, emptyList()) decryptDebug = MessageCrypto.AttachmentDecryptDebugResult(plain, emptyList())
plain plain
} else { } else {
@@ -2968,6 +2975,11 @@ internal suspend fun downloadAndDecryptImage(
} else if (isGroupStoredKey(chachaKey)) { } else if (isGroupStoredKey(chachaKey)) {
val groupPassword = decodeGroupPassword(chachaKey, privateKey) ?: return@withContext null val groupPassword = decodeGroupPassword(chachaKey, privateKey) ?: return@withContext null
CryptoManager.decryptWithPassword(encryptedContent, groupPassword) CryptoManager.decryptWithPassword(encryptedContent, groupPassword)
?: run {
val hexKey = groupPassword.toByteArray(Charsets.ISO_8859_1)
.joinToString("") { "%02x".format(it.toInt() and 0xff) }
CryptoManager.decryptWithPassword(encryptedContent, hexKey)
}
} else { } else {
val keyCandidates = MessageCrypto.decryptKeyFromSenderCandidates(chachaKey, privateKey) val keyCandidates = MessageCrypto.decryptKeyFromSenderCandidates(chachaKey, privateKey)
if (keyCandidates.isEmpty()) return@withContext null if (keyCandidates.isEmpty()) return@withContext null

View File

@@ -8,9 +8,22 @@ object AttachmentDownloadDebugLogger {
private val _logs = MutableStateFlow<List<String>>(emptyList()) private val _logs = MutableStateFlow<List<String>>(emptyList())
val logs: StateFlow<List<String>> = _logs.asStateFlow() val logs: StateFlow<List<String>> = _logs.asStateFlow()
fun log(@Suppress("UNUSED_PARAMETER") message: String) { private var appContext: android.content.Context? = null
// Disabled by request: no runtime accumulation of photo debug logs.
return fun init(context: android.content.Context) {
appContext = context.applicationContext
}
fun log(message: String) {
val ctx = appContext ?: return
try {
val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date())
val line = "$ts [PhotoDL] $message"
android.util.Log.d("PhotoDL", message)
val dir = java.io.File(ctx.filesDir, "crash_reports")
if (!dir.exists()) dir.mkdirs()
java.io.File(dir, "image_logs.txt").appendText("$line\n")
} catch (_: Exception) {}
} }
fun clear() { fun clear() {

View File

@@ -984,7 +984,7 @@ private fun viewerLog(context: Context, msg: String) {
val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date()) val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date())
val dir = java.io.File(context.filesDir, "crash_reports") val dir = java.io.File(context.filesDir, "crash_reports")
if (!dir.exists()) dir.mkdirs() if (!dir.exists()) dir.mkdirs()
java.io.File(dir, "rosettadev1.txt").appendText("$ts [Viewer] $msg\n") java.io.File(dir, "image_logs.txt").appendText("$ts [Viewer] $msg\n")
} catch (_: Exception) {} } catch (_: Exception) {}
} }
@@ -1062,7 +1062,14 @@ private suspend fun loadBitmapForViewerImage(
val groupPassword = CryptoManager.decryptWithPassword( val groupPassword = CryptoManager.decryptWithPassword(
image.chachaKey.removePrefix("group:"), privateKey image.chachaKey.removePrefix("group:"), privateKey
) )
if (groupPassword != null) CryptoManager.decryptWithPassword(encryptedContent, groupPassword) else null if (groupPassword != null) {
CryptoManager.decryptWithPassword(encryptedContent, groupPassword)
?: run {
val hexKey = groupPassword.toByteArray(Charsets.ISO_8859_1)
.joinToString("") { "%02x".format(it.toInt() and 0xff) }
CryptoManager.decryptWithPassword(encryptedContent, hexKey)
}
} else null
} else if (image.chachaKey.isNotEmpty()) { } else if (image.chachaKey.isNotEmpty()) {
val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(image.chachaKey, privateKey) val decryptedKeyAndNonce = MessageCrypto.decryptKeyFromSender(image.chachaKey, privateKey)
MessageCrypto.decryptAttachmentBlobWithPlainKey(encryptedContent, decryptedKeyAndNonce) MessageCrypto.decryptAttachmentBlobWithPlainKey(encryptedContent, decryptedKeyAndNonce)