Skip to content

webdotG/kharon-mess

Repository files navigation

KHARON MESSENGER

Установка через F-Droid

  1. Установи F-Droid
  2. Settings → Repositories → +
  3. Добавь репозиторий: https://webdotg.github.io/kharon-fdroid/repo
  4. Найди Kharon Messenger и установи

License: MIT Android F-Droid Self-hosted

Эфемерный. Зашифрованный. Без слежки. На своём железе.

· English · 中文 · العربية

Kharon — минималистичный зашифрованный мессенджер для небольших команд.
Сообщения живут 2 минуты, потом исчезают навсегда.
Никаких аккаунтов, телефонных номеров, истории переписки и чужих серверов.

Назван в честь Харона — перевозчика мёртвых в греческой мифологии. Что пересекает реку — не возвращается.


Идея

Большинство мессенджеров хранят сообщения на сервере, знают кто ты, и могут быть вынуждены передать данные третьим лицам.
Kharon построен на противоположном принципе:

  • Сервер — тупой relay: маршрутизирует зашифрованные блобы и больше ничего не знает
  • Ты сам держишь сервер — на своём железе
  • Сообщения самоуничтожаются через 2 минуты — история отсутствует по замыслу
  • Нет аккаунтов — твоя личность это криптографическая пара ключей, ничего больше
  • Добавить контакт = обменяться публичными ключами напрямую, офлайн или через копипаст

Как проходит сообщение

[Твой телефон]
  1. Вводишь текст
  2. Шифруешь публичным ключом получателя (X25519 + XSalsa20-Poly1305)
  3. Случайный nonce 24 байта prepend к шифртексту
  4. Отправляешь base64 блоб через WSS

       ↓  TLS 1.3  ↓

[Nginx]
  5. TLS termination
  6. Rate limiting, лимит соединений
  7. Проверка заголовка Upgrade: websocket
  8. Проксирует в Node.js

[Node.js relay]
  9. Получает base64 блоб — НИКОГДА не видит plaintext
  10. Маршрутизирует в WebSocket соединение получателя
  11. Если получатель офлайн: хранит в RAM очереди (макс 2 мин TTL)
  12. Подтверждает доставку через ACK

       ↓  TLS 1.3  ↓

[Телефон получателя]
  13. Получает base64 блоб
  14. Расшифровывает своим секретным ключом
  15. Показывает текст
  16. Сообщение существует только в RAM — никогда не пишется на диск

На шаге 9 сервер получает примерно следующее:

{ "type": "msg", "to": "aGVs...", "payload": "bG9yZW0...", "id": "x7k2..." }

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


Архитектура безопасности

Криптография

Что Алгоритм Почему
Обмен ключами X25519 (Curve25519 DH) Быстрый, надёжный, широко аудирован
Шифрование XSalsa20-Poly1305 Аутентифицированное шифрование — обнаруживает подмену
Nonce 24 случайных байта на сообщение 192-бит энтропии, вероятность коллизии ≈ 0
Хранение ключей Android Keystore + EncryptedSharedPreferences Аппаратная защита, нельзя извлечь без рута
Хранение контактов Room + SQLCipher База зашифрована на диске

Что знает сервер

Данные Сервер видит?
Содержимое сообщений ❌ Никогда — только зашифрованный блоб
Личность отправителя ❌ Только публичный ключ (44 символа base64)
Личность получателя ❌ Только публичный ключ
История сообщений ❌ TTL 2 мин, потом удаляется из RAM
Твоё имя ❌ Нет регистрации
Твой номер телефона ❌ Не требуется
Твой IP адрес ⚠️ Да, в технических логах соединений
Частота общения ⚠️ Частота подключений видна оператору сервера

Где что хранится

Данные Место Зашифровано? TTL
Твоя пара ключей Android Keystore ✅ Аппаратно Навсегда
Имена контактов Room/SQLCipher ✅ AES-256 До удаления
Публичные ключи контактов Room/SQLCipher ✅ AES-256 До удаления
История сообщений Только RAM N/A (не хранится) Сессия
Очередь сообщений на сервере Только RAM ✅ E2E 2 минуты

Эшелонированная защита сервера

Интернет
   │
   ▼
[Nginx — Слой 1]
   ├── Только TLS 1.2/1.3 (SSL, TLS 1.0/1.1 отключены)
   ├── Сильные шифры: ECDHE + AES-GCM + ChaCha20
   ├── HSTS (max-age=63072000) — браузер запоминает на 2 года
   ├── Session tickets ВЫКЛЮЧЕНЫ — защита forward secrecy
   ├── Rate limit: 5 новых соединений/сек с IP (burst 10)
   ├── Лимит соединений: 10 одновременных с IP
   ├── Только WebSocket: не-WS запросы получают 426
   └── Security headers: X-Frame-Options, CSP, X-Content-Type-Options
   │
   ▼
[Node.js — Слой 2]
   ├── Hello timeout: 10 сек (нет hello = дисконнект)
   ├── Лимит IP соединений: 3 одновременных с IP
   ├── Token bucket: 3 сообщения/сек на соединение (человек не может быстрее)
   ├── Валидация pubKey: ровно 44 символа base64
   ├── Валидация payload: base64, 0–4096 байт
   ├── Нельзя написать самому себе
   ├── Лимит офлайн-очереди: 50 сообщений на пользователя
   └── Sweep TTL: один интервал чистит протухшие (не N таймеров)
   │
   ▼
[Очередь сообщений — Слой 3]
   └── Только память, TTL 2 минуты, без персистентности

Эшелонированная защита Android клиента

[Сеть]
   ├── Только WSS (cleartext запрещён в network_security_config.xml)
   ├── Certificate pinning (sha256/ВАШ_CERT_PIN)
   └── Exponential backoff reconnect (1с → 2с → 4с → ... → 30с макс)

[Хранилище]
   ├── Ключи: EncryptedSharedPreferences + Android Keystore (AES-256-GCM)
   ├── Контакты: Room + SQLCipher (AES-256)
   └── Сообщения: только RAM, никогда не пишутся на диск

[ОС]
   ├── FLAG_SECURE: нет скриншотов, скрыт в switcher задач
   ├── allowBackup=false: системный бэкап отключён
   ├── Обнаружение root: предупреждает пользователя при наличии su
   └── Минимум разрешений: только INTERNET + CAMERA

[Протокол]
   ├── E2E шифрование: сервер никогда не видит plaintext
   ├── Защита от replay-атак: дедупликация последних 100 ID сообщений
   └── Nonce: 24 случайных байта на сообщение, никогда не повторяются

От чего Kharon НЕ защищает

Честно о limitations:

  • Физический захват устройства — если кто-то взял твой разблокированный телефон, он видит текущую сессию чата (она в RAM). Сообщения из прошлых сессий уже исчезли.
  • Скомпрометированный оператор сервера — если ты не держишь сервер сам, оператор видит метаданные соединений (кто подключается, когда, как часто). Содержимое сообщений он читать не может.
  • Рутованное устройство — Android Keystore потенциально можно обойти на рутованных устройствах. Kharon предупреждает при обнаружении root.
  • Анализ трафика — наблюдатель сети видит что ты подключаешься к своему серверу, но не что ты говоришь. Размеры сообщений немного раскрывают длину текста.
  • Компрометация эндпоинта — если на твоём телефоне малварь с захватом экрана, ни один мессенджер не поможет.

Структура проекта

kharon-mess/
├── server/                         # Node.js relay сервер
│   ├── index.js                    # WebSocket сервер, обработка сообщений
│   ├── hub.js                      # Менеджер соединений, TTL sweep
│   ├── nginx/
│   │   └── nginx.conf              # Rate limiting, TLS, security headers
│   ├── docker-compose.yml
│   ├── Dockerfile
│   └── .env.example
│
└── android/                        # Android клиент (Kotlin + Jetpack Compose)
    └── app/src/main/java/com/kharon/messenger/
        ├── crypto/
        │   └── CryptoManager.kt        # X25519 + XSalsa20-Poly1305
        ├── network/
        │   └── KharonSocket.kt         # WebSocket + cert pinning + backoff
        ├── service/
        │   └── KharonForegroundService.kt  # Фоновый keepalive WebSocket
        ├── storage/
        │   └── KharonDatabase.kt       # Room + SQLCipher
        ├── di/
        │   └── AppModule.kt            # Hilt DI
        ├── model/
        │   └── Models.kt
        ├── viewmodel/
        │   ├── ChatViewModel.kt
        │   └── ContactsViewModel.kt
        └── ui/
            ├── theme/
            │   ├── KharonTheme.kt      # 5 тем: Modern, Win95, ICQ, Terminal Dark/Light
            │   └── KharonThemeProvider.kt
            └── screens/
                ├── ContactsScreen.kt
                ├── ChatScreen.kt
                ├── AddContactScreen.kt
                └── SettingsScreen.kt

Развёртывание сервера

Требования

  • Docker + docker-compose
  • Домен или статический IP
  • TLS сертификат

Быстрый старт

git clone https://github.com/yourname/kharon-mess
cd kharon-mess/server

cp .env.example .env

# Получаем TLS сертификат (Let's Encrypt)
certbot certonly --standalone -d yourdomain.com

mkdir certs
cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem certs/
cp /etc/letsencrypt/live/yourdomain.com/privkey.pem certs/

docker compose up -d

Хук автообновления сертификата

# /etc/letsencrypt/renewal-hooks/deploy/kharon.sh
cp /etc/letsencrypt/live/yourdomain.com/fullchain.pem /path/to/server/certs/
cp /etc/letsencrypt/live/yourdomain.com/privkey.pem /path/to/server/certs/
docker compose -f /path/to/server/docker-compose.yml restart nginx

Переменные окружения

Переменная По умолчанию Описание
MSG_TTL_MS 120000 Время жизни сообщения в мс (2 мин)
SWEEP_INTERVAL_MS 30000 Интервал очистки протухших сообщений
MAX_MSG_SIZE 4096 Максимальный размер payload в байтах
MAX_QUEUE_PER_USER 50 Максимум офлайн-очереди на пользователя
MAX_CONNS_PER_IP 3 Максимум одновременных соединений с IP
MSG_RATE_LIMIT 3 Сообщений в секунду на соединение
MSG_BUCKET_MAX 9 Размер token bucket (запас для burst)

Android клиент

Сборка

cd android
./gradlew assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk

Certificate pinning

После развёртывания своего сервера обнови хэш в KharonSocket.kt:

openssl s_client -connect yourdomain.com:443 2>/dev/null \
  | openssl x509 -pubkey -noout \
  | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256 -binary \
  | base64

Замени хэш в:

.add("yourdomain.com", "sha256/ВАШ_ХЭШ_ЗДЕСЬ")

Добавление контакта

Два способа:

  1. Копипаст ключа — Add Contact → My Key → скопировать публичный ключ → отправить коллеге любым каналом → коллега вставляет в Add Contact → Enter Key
  2. QR код — показываешь свой QR, коллега сканирует

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


Темы оформления

Тема Стиль
Modern Чистый тёмный интерфейс
Windows 95 Классический ретро UI с рельефными кнопками
ICQ Та самая аська, зелёный цветочек
Terminal Dark Зелёный на чёрном, стиль Matrix/MikroTik
Terminal Light Тёмно-зелёный на белом, стиль DEC терминала

Планы развития

  • Групповые чаты
  • Push-to-talk (режим рации)
  • iOS клиент
  • Удаление сообщения до истечения TTL
  • Закреплённые сообщения (макс 24ч)
  • Подключение через VPN туннель

Зависимости

Сервер

  • ws — WebSocket сервер
  • dotenv — конфигурация окружения
  • Node.js 20+, Docker

Android

  • lazysodium-android — обёртка libsodium (X25519, XSalsa20)
  • okhttp — WebSocket клиент с certificate pinning
  • room + sqlcipher — зашифрованная локальная БД
  • security-crypto — обёртка Android Keystore
  • zxing-android-embedded — сканер QR кодов
  • hilt — dependency injection
  • Jetpack Compose — UI

Лицензия

MIT — делай что хочешь, поднимай свой инстанс, форкай, присылай PR.


Почему Харон?

Брал что давали, перевозил на ту сторону — и на этом всё. Никаких записей. Без возврата.

Именно так работает эта программа.


Roadmap / Пожелания к проекту

Хочешь улучшить Kharon? Вот что можно сделать:

Высокий приоритет

  • Push-уведомления — сейчас сообщения приходят только пока приложение открыто. Нужен FCM или UnifiedPush
  • iOS клиент — Swift + SwiftUI, та же криптография через libsodium
  • Forward Secrecy — Double Ratchet алгоритм как в Signal для перфектной прямой секретности

Средний приоритет

  • Групповые чаты — broadcast шифрование для N участников
  • Голосовые сообщения — запись + шифрование аудио
  • Подтверждение доставки — двойная галочка когда получатель онлайн
  • Удаление сообщений — до истечения TTL
  • Закреплённые сообщения — максимум 24 часа

Инфраструктура

  • Федерация серверов — несколько серверов, сообщения маршрутизируются между ними
  • Tor/I2P поддержка — скрытый сервис для полной анонимности
  • Reproducible builds — воспроизводимая сборка для F-Droid
  • GitHub Actions CI — автоматическая сборка и публикация APK

UX

  • Тёмный режим системы — автоматическое переключение темы
  • Экспорт/импорт контактов — QR-код со всеми контактами
  • Виджет — статус подключения на рабочем столе

Если хочешь реализовать что-то из этого — fork + PR, всё приветствуется.

!!! и так скромненько почти мелким шрифтом после всего главного

KHARON MESSENGER v2.0 (Architect Edition)

Что изменилось (v2.0):

  1. Protocol: Reception Hours — Клиент больше не обязан висеть в сети 24/7.
  2. Server: Signal queue_end — Сервер научился понимать режимы (PULSE/LIVE) и давать команду на разрыв соединения, когда все офлайн-сообщения доставлены.
  3. Architecture: Zero-Footprint — Теперь мессенджер может работать сессиями. Зашел -> Забрал -> Ушел.

Что не трогали:

  • E2EE: Шифрование осталось прежним. Ключи только у юзеров.
  • Traffic: Весь трафик идет через WebSocket с certificate pinning.
  • Rate Limits: Защита от спама на сервере (Token Bucket) работает как и раньше.

Как это работает:

  1. Клиент ставит "Сеанс: 60 мин".
  2. Сервис Android закрывается.
  3. Через 60 минут система будит сервис.
  4. Сервис за 2 секунды забирает все сообщения с сервера.
  5. Сервер говорит "Очередь пуста".
  6. Клиент засыпает еще на час.

About

Ephemeral E2E encrypted messenger. Self-hosted. Zero-knowledge.

Resources

License

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors