diff --git a/lib/domain/services/crypto_service.dart b/lib/domain/services/crypto_service.dart index 94a3a01..d0ba8ab 100644 --- a/lib/domain/services/crypto_service.dart +++ b/lib/domain/services/crypto_service.dart @@ -60,24 +60,33 @@ class CryptoService { String encryptedPrivateKey, String masterPassword, ) async { + final encryptedData = base64Decode(encryptedPrivateKey); + + // Разделяем nonce и зашифрованные данные + final nonce = encryptedData.sublist(0, 12); // GCM nonce = 12 bytes + final macBytes = encryptedData.sublist(12, 28); + final cipherText = encryptedData.sublist(28); + + // 1. Попытка дешифрации с новым значением итераций (600 000) try { - final encryptedData = base64Decode(encryptedPrivateKey); - - // Разделяем nonce и зашифрованные данные - final nonce = encryptedData.sublist(0, 12); // GCM nonce = 12 bytes - final macBytes = encryptedData.sublist(12, 28); - final cipherText = encryptedData.sublist(28); - - final masterKey = await _deriveKeyFromPassword(masterPassword); - + final masterKey = await _deriveKeyFromPassword(masterPassword, iterations: 600000); final decrypted = await aesGcm.decrypt( SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)), secretKey: masterKey, ); - return base64Encode(decrypted); - } catch (e) { - throw Exception('Неверный мастер-пароль или поврежденные данные'); + } catch (_) { + // 2. Если не удалось, пробуем старое значение (10 000) для обратной совместимости + try { + final masterKey = await _deriveKeyFromPassword(masterPassword, iterations: 10000); + final decrypted = await aesGcm.decrypt( + SecretBox(cipherText, nonce: nonce, mac: Mac(macBytes)), + secretKey: masterKey, + ); + return base64Encode(decrypted); + } catch (e) { + throw Exception('Неверный мастер-пароль или поврежденные данные'); + } } } @@ -98,10 +107,10 @@ class CryptoService { return base64Encode(encryptedData); } - Future _deriveKeyFromPassword(String password) async { + Future _deriveKeyFromPassword(String password, {int iterations = 600000}) async { final pbkdf2 = Pbkdf2( macAlgorithm: Hmac.sha256(), - iterations: 10000, + iterations: iterations, bits: 256, ); diff --git a/lib/logic/auth_provider.dart b/lib/logic/auth_provider.dart index a9778f9..933758c 100644 --- a/lib/logic/auth_provider.dart +++ b/lib/logic/auth_provider.dart @@ -489,6 +489,11 @@ class AuthProvider extends ChangeNotifier { // Метод для начала с чистого листа (новые ключи) Future resetKeys() async { + try { + await ApiService().clearOtherSessions(); + } catch (e) { + print("Error clearing other sessions on key reset: $e"); + } await _storage.delete(key: 'private_key'); try { final allKeys = await _storage.readAll(); diff --git a/lib/presentation/screens/chat_screen.dart b/lib/presentation/screens/chat_screen.dart index a512c37..8e31c0f 100644 --- a/lib/presentation/screens/chat_screen.dart +++ b/lib/presentation/screens/chat_screen.dart @@ -1535,7 +1535,7 @@ class _ChatScreenState extends State with RouteAware { sharedSecret, ); - final content50 = newText.length > 50 ? newText.substring(0, 50) : newText; + final content50 = newText.length > 500 ? newText.substring(0, 500) : newText; final encryptedContent50 = await _cryptoService.encryptMessage( content50, sharedSecret, @@ -2726,7 +2726,7 @@ class _ChatScreenState extends State with RouteAware { ); String previewText = rawText.isNotEmpty ? rawText : "[Фото]"; - if (previewText.length > 50) previewText = previewText.substring(0, 50); + if (previewText.length > 500) previewText = previewText.substring(0, 500); encryptedContent50 = await _cryptoService.encryptMessage( previewText, sharedSecret, diff --git a/lib/presentation/screens/security_settings_screen.dart b/lib/presentation/screens/security_settings_screen.dart index 8aa0fd6..fc4f225 100644 --- a/lib/presentation/screens/security_settings_screen.dart +++ b/lib/presentation/screens/security_settings_screen.dart @@ -175,6 +175,12 @@ class _SecuritySettingsScreenState extends State { ); if (!success) throw Exception('Сервер отклонил обновление крипто-ключа.'); + try { + await ApiService().clearOtherSessions(); + } catch (e) { + print("Error clearing other sessions on encryption password change: $e"); + } + if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -509,6 +515,30 @@ class _SecuritySettingsScreenState extends State { }, ), const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.warning_amber_rounded, + color: Theme.of(context).colorScheme.error, + size: 16, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Предупреждение: при изменении пароля шифрования все остальные активные сессии на других устройствах будут автоматически завершены.', + style: TextStyle( + color: Theme.of(context).colorScheme.error, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), SizedBox( width: double.infinity, child: ElevatedButton( diff --git a/srv/app/services/notification_service.py b/srv/app/services/notification_service.py index e31463a..db4c394 100644 --- a/srv/app/services/notification_service.py +++ b/srv/app/services/notification_service.py @@ -37,7 +37,7 @@ async def send_system_notification(db: AsyncSession, receiver_id: int, plain_tex # 2. Шифруем текст сообщения, используя сохраненный ключ encrypted_content = encrypt_system_message(plain_text, u_public_key) - preview_text = plain_text if len(plain_text) <= 50 else f"{plain_text[:47]}..." + preview_text = plain_text if len(plain_text) <= 500 else f"{plain_text[:497]}..." encrypted_preview = encrypt_system_message(preview_text, u_public_key) # 3. Записываем сообщение в историю базы данных diff --git a/srv/site/about-secutity/index.html b/srv/site/about-secutity/index.html new file mode 100644 index 0000000..d72d21d --- /dev/null +++ b/srv/site/about-secutity/index.html @@ -0,0 +1,719 @@ + + + + + + Архитектура Безопасности и Шифрования — Чепухаграм + + + + + + + + + + + +
+ +
+ + +
+
+

Безопасность и сквозное шифрование (E2EE)

+
Обновлено: Июнь 2026 • Документация Чепухаграм
+

В мессенджере Чепухаграм конфиденциальность переписки обеспечивается математическими законами. Никакие третьи лица, включая разработчиков и администраторов серверов, не могут получить доступ к содержимому ваших чатов.

+
+
+ + +
+ + +
+
+ 🔑 +

X25519

+

Протокол Диффи-Хеллмана для генерации общего ключа (ECDH)

+
+
+ ⚙️ +

AES-256-GCM

+

Симметричное шифрование с проверкой целостности данных

+
+
+ 🔒 +

PBKDF2

+

Криптографическая деривация ключей на базе мастер-пароля (600,000 ит.)

+
+
+ +

Принцип сквозного шифрования (E2EE)

+

Шифрование «end-to-end» означает, что шифрование информации происходит непосредственно на устройстве отправителя, а дешифрование — только на устройстве получателя. Серверная часть выполняет лишь функцию почтальона — пересылает зашифрованные пакеты байт, не имея ключей для их расшифровки.

+ + +

Схема согласования ключей (X25519)

+
+
+
Вы
+
Приватный ключ A
+
Публичный ключ A
+
Общий секрет (K)
+
+
+
публичный ключ A →
+
← публичный ключ B
+

(сервер пересылает только публичные ключи)

+
+
+
Собеседник
+
Приватный ключ B
+
Публичный ключ B
+
Общий секрет (K)
+
+
+ +
+
+
1
+
+

Создание пары ключей

+

При первом запуске приложения Чепухаграм генерирует на устройстве пару асимметричных ключей X25519 (приватный и публичный). Публичный ключ отправляется на сервер для того, чтобы другие пользователи могли начать с вами чат.

+
+
+
+
2
+
+

Деривация общего секрета (Shared Secret)

+

Когда вы открываете чат, приложение берет ваш приватный ключ X25519 и публичный ключ собеседника, вычисляя общий секрет по алгоритму ECDH. Этот секретный ключ никогда не передается по сети — обе стороны вычисляют его независимо на своих девайсах.

+
+
+
+
3
+
+

Симметричное шифрование

+

Все отправляемые текстовые сообщения шифруются по стандарту AES-256-GCM с уникальным вектором инициализации (Nonce). Полученный шифротекст передается на сервер.

+
+
+
+ +

Шифрование текстовых сообщений

+

Каждое текстовое сообщение кодируется в массив байтов, после чего для него генерируется случайный 12-байтовый вектор инициализации (nonce). Данные шифруются на ключе sharedSecret по алгоритму AES-256-GCM. Результат представляет собой склеенный массив байтов: nonce (12 байт) + mac (16 байт) + ciphertext (зашифрованный текст), закодированный в Base64.

+ +
+
// Схематичный пример дешифровки сообщения на клиенте
+Future<String> decryptMessage(String base64Data, SecretKey sharedKey) async {
+  final data = base64Decode(base64Data);
+
+  final nonce = data.sublist(0, 12);
+  final mac = data.sublist(12, 28);
+  final cipherText = data.sublist(28);
+
+  final decrypted = await aesGcm.decrypt(
+    SecretBox(cipherText, nonce: nonce, mac: Mac(mac)),
+    secretKey: sharedKey,
+  );
+
+  return utf8.decode(decrypted);
+}
+
+ +

Поблочное шифрование медиафайлов

+

Для предотвращения утечек данных при отправке медиафайлов (картинок, видео, голосовых заметок и документов), в Чепухаграм внедрена продвинутая поблочная система шифрования:

+
    +
  1. При выборе файла генерируется случайный симметричный ключ файла (fileKey).
  2. +
  3. Ключ fileKey шифруется на общем ключе чата (sharedSecret) и отправляется на сервер как поле encrypted_key.
  4. +
  5. Сам файл считывается потоком и шифруется блоками по 64 КБ с использованием fileKey. К каждому зашифрованному блоку добавляется 4-байтовый заголовок, содержащий точную длину блока, и уникальный вектор инициализации с тегом аутентификации MAC.
  6. +
+ + +

Архитектура поблочного крипто-стрима

+
+
Исходный файл медиа
+
👇 Считывание блоками по 64 КБ
+
+
+ Блок 1 (64 КБ) +
🔒 AES-256-GCM (fileKey)
+
Длина (4б) + Nonce (12б) + Данные + MAC (16б)
+
+
+ Блок 2 (64 КБ) +
🔒 AES-256-GCM (fileKey)
+
Длина (4б) + Nonce (12б) + Данные + MAC (16б)
+
+
+ Остаток (<64 КБ) +
🔒 AES-256-GCM (fileKey)
+
Длина (4б) + Nonce (12б) + Данные + MAC (16б)
+
+
+
+ +
+

Важно: Такой подход позволяет осуществлять потоковую дешифрацию медиа во время загрузки (стриминг), благодаря чему видео и аудиозаписи начинают воспроизводиться еще до полной загрузки файла.

+
+ +

Резервная копия ключей и мастер-пароль

+

Поскольку приватный ключ X25519 хранится в изолированном защищенном хранилище (Secure Storage) вашего смартфона, при переустановке приложения вы можете потерять доступ к переписке. Для предотвращения этого в Чепухаграм создана система защищенных резервных копий:

+
    +
  • Вы придумываете сложный Мастер-пароль.
  • +
  • На основе этого пароля с помощью алгоритма PBKDF2 (600 000 итераций, HMAC-SHA256, соль "chepuhagram_salt") генерируется ключ шифрования резервной копии.
  • +
  • Приватный ключ X25519 шифруется на этом ключе и отправляется на сервер как encrypted_private_key.
  • +
  • При входе на новом устройстве вы вводите мастер-пароль, приложение скачивает копию ключа, расшифровывает её на вашем устройстве, и вы снова можете читать все ваши чаты. Сервер видит только зашифрованный массив байт и не может его декодировать.
  • +
+ +

Безопасность сессий и автозавершение

+

Чепухаграм поддерживает одновременный вход на нескольких устройствах. Вся сессионная активность полностью подконтрольна пользователю:

+

При любом изменении пароля сквозного шифрования (или при полном сбросе ключей) все остальные активные сессии на сторонних девайсах автоматически инвалидируются на сервере и прекращают работу. Это защищает ваши чаты от несанкционированного доступа с ранее авторизованных устройств.

+
+ + +
+
+ + +
+
+ + + diff --git a/srv/site/index.html b/srv/site/index.html new file mode 100644 index 0000000..50c8efc --- /dev/null +++ b/srv/site/index.html @@ -0,0 +1,946 @@ + + + + + + Чепухаграм — Безопасное общение без компромиссов + + + + + + + + + + + +
+ +
+ + +
+
+
+

Чепухаграм

+

Приватность следующего поколения. Полноценное сквозное шифрование сообщений, звонков и медиафайлов. Полный контроль над вашими личными данными.

+ +
+ + +
+
+
+
09:41
+
📶 🔋
+
+
+
А
+
+

Алиса

+ в сети +
+
+
+
+ Привет! Ты проверил систему шифрования Чепухаграм? + 22:04 +
+
+ Да! Используется X25519 для обмена ключами и AES-256-GCM для шифрования. + 22:05 +
+
+ Отлично! А что с файлами и звонками? + 22:05 +
+
+
+
Напишите сообщение...
+
+
+
+
+
+
+
+ + +
+
+
+

Возможности Чепухаграм

+

Сочетание передовых криптографических технологий с удобным современным интерфейсом мессенджера.

+
+ +
+ +
+
🔒
+

Сквозное шифрование

+

Текстовые сообщения и метаданные шифруются прямо на вашем устройстве и передаются в зашифрованном виде. Сервер не имеет ключей для расшифровки.

+
+ + +
+
🎙️
+

Голос и Видео-кружочки

+

Записывайте голосовые сообщения и видео-кружочки в одно касание. Нативное сжатие через FFmpeg уменьшает вес медиа без потери качества.

+
+ + +
+
📤
+

Стриминг файлов

+

Безопасный поблочный стриминг файлов любого формата. Каждый файл шифруется своим уникальным AES-GCM ключом, который передается через E2EE канал.

+
+ + +
+
📞
+

Аудио и видеозвонки

+

Кристально чистые и конфиденциальные WebRTC звонки напрямую между вашими устройствами в обход промежуточных серверов.

+
+
+
+
+ + +
+
+ +
+
+ + + + + + + +