← Umbera

Доказательство · открытый код

Не верьте на слово — проверьте

Любой мессенджер говорит «мы не читаем ваши сообщения». Мы решили не просить верить, а дать проверить: код открыт, тесты запускает любой, звонок можно перехватить Wireshark и убедиться самому.

4 октября 2026 · 7 минут чтения · docs/PROOF.md ↗

Коротко

  • Каждое сообщение запечатывается на телефоне отправителя сразу двумя алгоритмами: X25519 и постквантовым ML-KEM-1024 (Kyber). Чтобы прочитать, нужно взломать оба.
  • Сервер получает адрес ящика и шифротекст. Ни текста, ни отправителя, ни получателя в нём нет.
  • Звук и видео шифруются DTLS-SRTP между двумя телефонами. Ретранслятор пересылает пакеты, которые не может открыть.
  • Всё это проверяется: код на GitHub, 7 тестов запускаются одной командой, звонок проверяется в Wireshark.

Сообщения: как запечатывается каждое

Схема запечатывания сообщения: ratchet → внутренний пакет → печать X25519 + Kyber → выравнивание размера → доставка

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

Этот пакет запечатывается получателю: на каждое сообщение создаётся новый одноразовый ключ X25519 и делается инкапсуляция ML-KEM-1024. Оба секрета сводятся в один ключ через HKDF-SHA256, а пакет закрывается AES-256-GCM. Результат дополняется до фиксированного размера, чтобы длина не выдавала содержимое.

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

Rust · core/tests/proof.rsGitHub ↗
fn seal(to: &Recipient, plaintext: &[u8]) -> Sealed {
    let ephemeral_private = random::<32>();
    let ephemeral_public = x25519_public_key(ephemeral_private.clone()).unwrap();
    let ss1 = x25519_shared_secret(ephemeral_private, to.x25519_public.clone()).unwrap();
    let enc = mlkem1024_encapsulate(to.kyber_public.clone()).unwrap();
    let iv = random::<12>();
    let box_ = aes256_gcm_encrypt(key(ss1, enc.shared_secret), iv.clone(), plaintext.to_vec(), vec![]).unwrap();
    Sealed { ephemeral_public, kyber_ciphertext: enc.ciphertext, iv, box_ }
}
Запечатывание из теста-доказательства — та же схема, что в приложениях

MessageEncryption.kt → createSealedSenderHybridPQ ↗

Что видит сервер

ДанныеСервер видит?
Текст сообщения, фото, голосовыене видит нет — только шифротекст
Кто отправилне видит нет — отправитель спрятан внутри конверта
Комуне видит нет — только адрес ящика, который вычисляют лишь двое и который меняется каждую неделю
Точный размерне видит нет — только размерная «корзина»
Время доставкивидит да — без него нельзя доставить
IP-адресвидит да, как любой сайт — или нет, если включить Tor в настройках

Мы специально показываем и то, что сервер видит. Доказательство, которое скрывает неудобное, — это реклама.

7 тестов, которые запускает любой

В репозитории лежит файл tests/proof.rs. Он воспроизводит ту самую печать, которой закрывается каждое сообщение, и проверяет каждое свойство отдельным тестом. Вот главный — постквантовая гарантия:

Rust · core/tests/proof.rsGitHub ↗
#[test]
fn breaking_only_x25519_is_not_enough() {
    let bob = new_recipient();
    let eve = new_recipient();
    let sealed = seal(&bob, MESSAGE);
    assert_eq!(open(&bob.x25519_private, &eve.kyber_private, &sealed), None);
}
Ключ X25519 получателя у атакующего есть, ключа Kyber — нет. Сообщение не открывается.

Этот тест моделирует будущий квантовый компьютер, который взломал X25519. Даже тогда сообщение не открыть без ключа Kyber, а он устойчив к квантовым атакам. Отдельные тесты проверяют: чужие ключи не подходят, изменение хоть одного бита отклоняется, одинаковый текст дважды даёт несвязанные шифротексты.

Проверьте сами — три команды

Нужен только Rust (rustup.rs). Сборка и тесты идут на Linux, macOS и Windows.

bash
git clone https://github.com/Vahe327/UMBERA
cd UMBERA/core
cargo test --test proof
output
running 7 tests
test the_recipient_can_read_the_message ... ok
test the_ciphertext_does_not_contain_the_message ... ok
test anyone_with_other_keys_cannot_read_it ... ok
test breaking_only_x25519_is_not_enough ... ok
test breaking_only_kyber_is_not_enough ... ok
test a_tampered_message_is_rejected ... ok
test every_message_is_sealed_with_fresh_keys ... ok

test result: ok. 7 passed; 0 failed
Результат на нашем компьютере. На вашем должно быть то же самое.

Звонки: ключи только у двух телефонов

Схема звонка: телефон Алисы ↔ ретранслятор ↔ телефон Боба, между ними только зашифрованный SRTP

Звук и видео шифруются DTLS-SRTP — это шифрование, встроенное в WebRTC. Рукопожатие DTLS идёт прямо между двумя телефонами, поэтому ключи разговора рождаются на телефонах и никуда не уходят.

Если прямое соединение невозможно, пакеты идут через ретранслятор (TURN). Он не участвует в рукопожатии, ключей не имеет и пересылает только зашифрованные пакеты.

Подслушать можно было бы одним способом: подменить отпечатки DTLS при установке звонка (атака «человек посередине»). Поэтому в Umbera установка звонка шифруется той же схемой, что и сообщения, и подписывается звонящим. Сервер не может ни прочитать отпечатки, ни изменить их, не сломав подпись.

Групповые звонки соединяют участников напрямую друг с другом. Сервера, который смешивает или декодирует звук, нет.

Kotlin · call/CallSignaling.ktGitHub ↗
val result = messageEncryption.encryptMessage(
    content = data,
    recipientId = peerUserId,
    recipientPublicKey = peerKeys.first,
    recipientX25519PublicKey = peerKeys.second,
    type = "signaling",
    sequenceNumber = 0
)
Описание звонка (SDP с отпечатками DTLS) шифруется как сообщение
Kotlin · crypto/MessageEncryption.ktGitHub ↗
if (!Secp256k1.verify(ciphertext, sig, senderPublicKey)) {
    throw SecurityException("Invalid message signature")
}
Чужая или изменённая подпись — звонок не устанавливается

Проверка звонка в Wireshark

  1. Подключите телефон к Wi-Fi, который вы контролируете, и запишите его трафик Wireshark — например, на ноутбуке, раздающем интернет.
  2. Сделайте звонок в Umbera.
  3. Поставьте фильтр dtls || rtp || srtp.
  4. Вы увидите рукопожатие DTLS и дальше только зашифрованные пакеты SRTP. Ни звука, ни видео Wireshark из них не достанет, открытого RTP нет.

Честные допущения

Доказательство честно ровно настолько, насколько честны его условия. Вот они:

Телефоны участников не зараженыВирус на устройстве видит экран — от этого не защитит ни один мессенджер.
Получатель сам не пересылает перепискуШифрование не может запретить человеку поделиться тем, что он получил.
Стандартные алгоритмы надёжныML-KEM-1024, X25519, AES-256-GCM, HKDF-SHA256, DTLS-SRTP — стандарты NIST и IETF, свои шифры мы не изобретаем.
Ключи важного контакта свереныПрозрачность ключей и закрепление ключей предупредят, если ключи собеседника сменились.

Что опубликовано

Криптографическое ядро на Rust (те же библиотеки RustCrypto, dalek и OpenMLS, что и в приложениях) и протокольный слой на Kotlin — в том виде, в каком он работает в приложениях Android и iOS. Плюс полная спецификация протокола.

Сервер и интерфейс приложения в репозиторий не входят. Сервер получает только то, что этот код уже зашифровал, поэтому для проверки утверждений он не нужен.

Лицензия PolyForm Strict 1.0.0: читать, собирать, запускать тесты и проверять может любой.

Нашли уязвимость?

Если вы сможете опровергнуть хотя бы одно утверждение, мы хотим узнать об этом первыми. Как сообщить — в SECURITY.md репозитория. Отвечаем на каждое сообщение.

Поделиться доказательством

XTelegramLinkedInRedditHacker News

Приватность, которую можно проверить

Без номера телефона и почты. Постквантовое шифрование каждого сообщения.