Files
mobile-android/docs/ENCRYPTED_STORAGE_UPDATE.md
k1ngsterr1 569aa34432 feat: Add comprehensive encryption architecture documentation for Rosette Messenger
feat: Implement Firebase Cloud Messaging (FCM) integration documentation for push notifications

docs: Outline remaining tasks for complete FCM integration in the project

fix: Resolve WebSocket connection issues after user registration
2026-01-17 19:04:05 +05:00

9.6 KiB
Raw Permalink Blame History

Обновление: Шифрование сообщений в базе данных

📋 Краткое описание

Реализовано шифрование поля plainMessage в базе данных, как это сделано в архивной версии приложения. Теперь чистый текст сообщений НЕ хранится в базе данных - только зашифрованная версия.

🔒 Система безопасности

До изменений

// ❌ Открытый текст в БД
plainMessage = "Hello, this is my message"  // Уязвимость!

После изменений

// ✅ Зашифрованный текст в БД
plainMessage = "ivBase64:encryptedDataBase64"  // AES-256-CBC + PBKDF2

🎯 Архитектура шифрования

Схема "Матрешка" (как в архивной версии)

┌─────────────────────────────────────────────────────────┐
│ 1⃣ Сетевой слой (E2E шифрование)                         │
│    content: XChaCha20-Poly1305                          │
│    chachaKey: ECDH + AES                                │
└─────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────┐
│ 2⃣ Локальное хранилище (дополнительная защита)           │
│    plainMessage: AES-256-CBC + PBKDF2                   │
│    Ключ шифрования: приватный ключ пользователя         │
└─────────────────────────────────────────────────────────┘

🔧 Технические детали

Алгоритм шифрования

  • Алгоритм: AES-256-CBC
  • Деривация ключа: PBKDF2-HMAC-SHA1
  • Salt: "rosetta"
  • Итерации: 1000
  • Сжатие: zlib deflate (RAW, без header)
  • Формат: base64(IV):base64(ciphertext)

Ключ шифрования

val encryptedPlainMessage = CryptoManager.encryptWithPassword(
    data = plainText,
    password = privateKey  // 64-символьный hex приватного ключа
)

📝 Изменённые файлы

1. MessageRepository.kt

Отправка сообщений:

// Шифруем plainMessage перед сохранением
val encryptedPlainMessage = CryptoManager.encryptWithPassword(text.trim(), privateKey)

val entity = MessageEntity(
    // ...
    plainMessage = encryptedPlainMessage,  // 🔒 Зашифрованный
    // ...
)

Приём сообщений:

// Шифруем plainMessage входящего сообщения
val encryptedPlainMessage = CryptoManager.encryptWithPassword(plainText, privateKey)

val entity = MessageEntity(
    // ...
    plainMessage = encryptedPlainMessage,  // 🔒 Зашифрованный
    // ...
)

Чтение из БД:

private fun MessageEntity.toMessage(): Message {
    // Расшифровываем при чтении
    val decryptedText = if (privateKey != null && plainMessage.isNotEmpty()) {
        CryptoManager.decryptWithPassword(plainMessage, privateKey) ?: plainMessage
    } else {
        plainMessage
    }

    return Message(
        // ...
        content = decryptedText,  // 🔓 Расшифрованный для UI
        // ...
    )
}

2. ChatViewModel.kt

Сохранение в БД:

private suspend fun saveMessageToDatabase(...) {
    // Шифруем plainMessage
    val encryptedPlainMessage = CryptoManager.encryptWithPassword(text, privateKey)

    val entity = MessageEntity(
        // ...
        plainMessage = encryptedPlainMessage,  // 🔒 Зашифрованный
        // ...
    )
}

Отображение в UI:

private suspend fun entityToChatMessage(entity: MessageEntity): ChatMessage {
    var displayText = try {
        // Сначала пробуем расшифровать из content + chachaKey (приоритет)
        MessageCrypto.decryptIncoming(entity.content, entity.chachaKey, privateKey)
    } catch (e: Exception) {
        // Fallback: расшифровываем plainMessage
        CryptoManager.decryptWithPassword(entity.plainMessage, privateKey) ?: entity.plainMessage
    }

    return ChatMessage(text = displayText, ...)
}

3. MessageEntities.kt

Обновлён комментарий:

@ColumnInfo(name = "plain_message")
val plainMessage: String,  // 🔒 Зашифрованный текст (encryptWithPassword)

🛡️ Защита данных

Что защищено

  • Текст сообщений в БД (plainMessage)
  • Текст сообщений в сети (content)
  • Вложения (attachments)
  • Приватные ключи (в отдельной таблице)

Уровни защиты

  1. При компрометации БД без приватного ключа:

    • Злоумышленник видит только зашифрованные данные
    • Невозможно прочитать содержимое сообщений
    • Требуется приватный ключ (64 hex символа)
  2. При компрометации БД И приватного ключа:

    • Можно расшифровать plainMessage
    • НО content всё ещё защищён E2E шифрованием
    • Требуется дополнительный ключ собеседника (chachaKey)
  3. Полная компрометация:

    • Требуется: БД + приватный ключ + chachaKey + публичный ключ собеседника
    • Очень сложный вектор атаки

📊 Сравнение с архивной версией

Архивная версия (TypeScript)

const plainMessage = await encodeWithPassword(privatePlain, message.trim());

await runQuery(`
    INSERT INTO messages (..., plain_message, ...)
    VALUES (..., ?, ...)
`, [..., plainMessage, ...]);

Новая версия (Kotlin)

val encryptedPlainMessage = CryptoManager.encryptWithPassword(text.trim(), privateKey)

val entity = MessageEntity(
    // ...
    plainMessage = encryptedPlainMessage,
    // ...
)
messageDao.insertMessage(entity)

⚠️ Важные замечания

Совместимость

  • Полностью совместимо с JS/TypeScript версией
  • Использует те же алгоритмы (PBKDF2-HMAC-SHA1, AES-256-CBC)
  • Тот же формат данных (ivBase64:ciphertextBase64)

Производительность

  • Расшифровка происходит только при отображении в UI
  • Кэширование расшифрованных сообщений в памяти (decryptionCache)
  • PBKDF2 с 1000 итерациями - быстро на современных устройствах (~1-2ms)

Миграция данных

⚠️ ВНИМАНИЕ: Старые сообщения с незашифрованным plainMessage будут работать:

// Fallback в коде автоматически обрабатывает старый формат
val decryptedText = CryptoManager.decryptWithPassword(plainMessage, privateKey)
    ?: plainMessage  // Если расшифровка не удалась - используем как есть

🧪 Тестирование

Проверка шифрования

  1. Отправить сообщение
  2. Проверить БД: SELECT plain_message FROM messages LIMIT 1
  3. Должно быть: ivBase64:ciphertextBase64 (не читаемый текст)

Проверка расшифровки

  1. Открыть чат
  2. Сообщения должны отображаться корректно
  3. При выходе и входе - сообщения всё ещё читаемы

Проверка совместимости

  1. Отправить сообщение с Android
  2. Прочитать на Desktop/React Native версии
  3. Должно расшифроваться корректно

📚 Дополнительные материалы

  • ENCRYPTION_EXPLAINED.md - детальное описание всей системы шифрования
  • SECURITY.md - политика безопасности приложения
  • rosette-messenger-app/Архив/ - исходная реализация

Статус

  • Реализовано шифрование при сохранении
  • Реализована расшифровка при чтении
  • Обновлены комментарии в коде
  • Проверена компиляция
  • Проведено тестирование на устройстве
  • Проверена совместимость с другими версиями

Дата обновления: 13 января 2026
Автор: GitHub Copilot
Версия: 1.0.0