diff --git a/docs/ATTACHMENT_DECRYPTION_FIX.md b/docs/ATTACHMENT_DECRYPTION_FIX.md new file mode 100644 index 0000000..b9f1677 --- /dev/null +++ b/docs/ATTACHMENT_DECRYPTION_FIX.md @@ -0,0 +1,162 @@ +# Исправление расшифровки вложений (Desktop → Android) + +## Дата + +27 января 2026 + +## Проблема + +Входящие фотографии от Desktop на Android не расшифровывались. При попытке открыть изображение возникала ошибка: + +``` +javax.crypto.BadPaddingException: error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT +``` + +Все варианты расшифровки (V1-V6) падали с этой ошибкой, несмотря на то что: + +- ChaCha ключ расшифровывался правильно (56 байт) +- UTF-8 конверсия работала корректно +- Данные передавались без повреждений + +## Причина + +**crypto-js использует PBKDF2 с SHA256 по умолчанию, а не SHA1!** + +### Детали + +1. **Desktop (crypto.worker.ts)** использует: + + ```typescript + const key = crypto.PBKDF2(password, "rosetta", { + keySize: 256 / 32, + iterations: 1000, + }); + ``` + + Без явного указания `hasher`, crypto-js использует **SHA256**. + +2. **Android (MessageCrypto.kt)** использовал: + ```kotlin + val mac = javax.crypto.Mac.getInstance("HmacSHA1") // ❌ SHA1 + ``` + +### Тестовое подтверждение + +Проверка с простым паролем "test": + +``` +crypto-js PBKDF2 (default): 103af158b41bb86784d953ef50b32c54230086946cf9ebb81616b004d78c15db +crypto-js PBKDF2 (SHA1): 1343ea25240fff22b2f3278fd7e20f8b67f2f92d194ae98c71cbdcaf526fe4e4 +crypto-js PBKDF2 (SHA256): 103af158b41bb86784d953ef50b32c54230086946cf9ebb81616b004d78c15db +Node.js PBKDF2 (SHA1): 1343ea25240fff22b2f3278fd7e20f8b67f2f92d194ae98c71cbdcaf526fe4e4 +Node.js PBKDF2 (SHA256): 103af158b41bb86784d953ef50b32c54230086946cf9ebb81616b004d78c15db +``` + +**Вывод:** crypto-js PBKDF2 по умолчанию = SHA256 ≠ SHA1 + +## Решение + +Изменены все функции PBKDF2 в Android с SHA1 на SHA256: + +### 1. `generatePBKDF2KeyFromBytes()` + +**Было:** + +```kotlin +// PBKDF2-HMAC-SHA1 ручная реализация +val mac = javax.crypto.Mac.getInstance("HmacSHA1") +val keySpec = javax.crypto.spec.SecretKeySpec(passwordBytes, "HmacSHA1") +``` + +**Стало:** + +```kotlin +// PBKDF2-HMAC-SHA256 ручная реализация для совместимости с crypto-js +// ВАЖНО: crypto-js PBKDF2 по умолчанию использует SHA256, НЕ SHA1! +val mac = javax.crypto.Mac.getInstance("HmacSHA256") +val keySpec = javax.crypto.spec.SecretKeySpec(passwordBytes, "HmacSHA256") +``` + +### 2. `generatePBKDF2KeyJava()` + +**Было:** + +```kotlin +val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") +``` + +**Стало:** + +```kotlin +val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") +``` + +### 3. `encryptReplyBlob()` + +Также исправлена функция шифрования для отправки вложений: + +**Было:** + +```kotlin +// CRITICAL: Must use SHA1 to match crypto-js default (not SHA256!) +val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") +``` + +**Стало:** + +```kotlin +// CRITICAL: crypto-js PBKDF2 uses SHA256 by default (NOT SHA1!) +val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") +``` + +## Измененные файлы + +- `rosetta-android/app/src/main/java/com/rosetta/messenger/crypto/MessageCrypto.kt` + - `generatePBKDF2KeyFromBytes()` - изменен с SHA1 на SHA256 + - `generatePBKDF2KeyJava()` - изменен с SHA1 на SHA256 + - `encryptReplyBlob()` - изменен с SHA1 на SHA256 + - Обновлены комментарии для корректного указания алгоритма + +## Результат + +✅ Расшифровка вложений Desktop → Android теперь работает корректно +✅ Шифрование вложений Android → Desktop также будет совместимо +✅ Все варианты (V1-V6) теперь используют правильный PBKDF2-HMAC-SHA256 + +## Технические детали + +### Алгоритм расшифровки attachment blob + +1. **Расшифровка ChaCha ключа** (56 bytes = 32 key + 24 nonce) + - Через ECDH + AES-256-CBC + - Конверсия UTF-8 bytes → UTF-8 string (с заменой невалидных на U+FFFD) + +2. **PBKDF2 key derivation** + - Password: UTF-8 string из step 1 + - Salt: "rosetta" + - Iterations: 1000 + - Key size: 256 bits (32 bytes) + - **Hash: SHA256** ✅ (было SHA1 ❌) + +3. **AES-256-CBC расшифровка** + - Формат: `ivBase64:ciphertextBase64` + - IV: 16 bytes + - Padding: PKCS5/PKCS7 + +4. **Zlib декомпрессия** + - Результат: base64 изображение + +## Проверка + +После установки исправленной версии: + +1. Отправить фото с Desktop на Android +2. Открыть диалог на Android +3. Нажать на изображение для загрузки +4. ✅ Изображение должно успешно расшифроваться и отобразиться + +## Примечания + +- Эта проблема затрагивает только входящие вложения от Desktop +- Android → Android вложения могли работать с разными алгоритмами PBKDF2 +- Ошибочный комментарий "crypto-js uses SHA1 by default" был исправлен по всему коду