Мы не можем прочитать ваши секреты — даже если захотим. Пройдите этот сценарий и убедитесь сами.
Участники
Кто участвует в сценарии
В реальном сервисе это настоящие пользователи. Здесь мы сыграем за всех, чтобы показать полный цикл — от создания до расшифровки.
✓
👨
Иван
Создаёт секрет
публичный
—
приватный
● ● ● ● скрыт
Только у Ивана
✓
👩
Катя
Доверенный человек
публичный
—
приватный
● ● ● ● скрыт
Только у Кати
✓
👨💼
Михаил
Доверенный человек
публичный
—
приватный
● ● ● ● скрыт
Только у Михаила
✓
🧑🔧
Дмитрий
Доверенный человек
публичный
—
приватный
● ● ● ● скрыт
Только у Дмитрия
✓
👩💻
Настя
Получатель секрета
публичный
—
приватный
● ● ● ● скрыт
Только у Насти
У каждого — пара ключей: публичный (как замок — его можно раздать всем) и приватный (только у владельца — это ключ от замка). Иван запечатает данные замком Насти — открыть сможет только она.
В реальном сервисе ключи создаются один раз при регистрации и сохраняются в браузере пользователя. На сервер приватные ключи не уходят никогда.
Настя сразу получит уведомление, что для неё пришло сообщение — но не увидит ни от кого, ни что внутри. Прочитать его она сможет только когда сработает триггер раскрытия: например, Иван не выходил на связь дольше заданного времени (пропустил чек-ин). Если настроены доверенные люди — они дополнительно подтверждают, что это не случайность. Только тогда Настя получает доступ к содержимому.
▶Для любопытных — технические подробности
Каждому участнику генерируется пара ключей X25519:
публичный — отправляется на сервер (видит кто угодно)
приватный — остаётся в браузере, на сервер не уходит
X25519 — протокол обмена ключами Диффи–Хеллмана
на эллиптической кривой Curve25519 (256 бит).
Используется в Signal, WireGuard, TLS 1.3.
На его основе строится Sealed Box — схема асимметричного
шифрования: зашифровать может любой, зная публичный ключ,
расшифровать — только владелец приватного.
Это как почтовый ящик: бросить письмо может кто угодно,
достать — только хозяин ключа от замка.
Генерируется прямо сейчас через libsodium — ту же библиотеку,
что использует мессенджер Signal.
Хранение в реальном сервисе:
Приватный ключ нельзя хранить открытым текстом — его нужно
зашифровать паролем пользователя. Но хранить raw-пароль тоже
нельзя. Поэтому из пароля сначала выводится ключ шифрования:
PBKDF2-SHA256 — функция вывода ключа из пароля.
Специально медленная: 100 000 внутренних SHA-256 операций
прежде чем получить финальный ключ (~100 мс на процессоре).
Цель: замедлить атаку перебором паролей в 100 000 раз.
Без PBKDF2: GPU проверяет ~10 млрд паролей в секунду.
С PBKDF2: GPU проверяет ~100 000 паролей в секунду.
Слабый пароль это не спасёт — но надёжный пароль
(12+ случайных символов) делает перебор нереальным
при любом раскладе. PBKDF2 — дополнительный барьер,
который защищает даже средние пароли.
Затем приватный ключ шифруется этим ключом через AES-256-GCM
и сохраняется в браузере. На сервер уходит только
зашифрованная копия (и только при включённой функции облачного
резервного хранения ключей) — без пароля расшифровать невозможно.
Шаг 0
Выберите сценарий
Нужны ли доверенные люди?
С доверенными: секрет откроется только когда нужное число доверенных людей подтвердит это.
Иван выбирает кто и при каких условиях может дать «добро» на передачу.
Без доверенных: Иван просто шифрует секрет прямо для Насти. Никакого дополнительного подтверждения не нужно —
Настя сможет открыть его сразу.
Сколько доверенных людей?
Сколько подтверждений нужно для открытия?
—
Ключи созданы — посмотрите на карточки участников выше. Нажмите на публичный ключ чтобы развернуть его полностью.
Шаг 1
Иван создаёт секрет
1 Система создаёт случайные ключи
—
▶Для любопытных
—
2 Ключ доверенных лиц делится на части
—
Ключ доверенных
—
→
—
▶Для любопытных
—
2 Текст секрета зашифрован
Текст запирается на математический замок. Каждый раз замок выглядит по-разному — даже если текст одинаковый. Это специальная защита: никто не может угадать, что текст повторяется.
Ваш текст → зашифрованный вид (то, что хранится на сервере)
—
Нажмите «Ещё раз» — результат изменится, хотя текст тот же. Каждый раз генерируется уникальная случайная «соль». Даже зная, что два секрета одинаковые — доказать это по зашифрованным данным невозможно.
Иван тоже сохраняет зашифрованную копию для своего Хранилища. Она зашифрована его собственным ключом, выведенным из его приватного ключа — никто другой её не откроет.
▶Для любопытных
Алгоритм: XSalsa20-Poly1305 (libsodium, crypto_secretbox_easy)
Из семейства шифров DJB (ChaCha20/Salsa20), используемых
в WireGuard и TLS 1.3. Стандарт libsodium по умолчанию.
XSalsa20 — потоковый шифр (ключ 256 бит, nonce 192 бит).
Генерирует псевдослучайный поток и XOR-ит его с текстом.
Одинаковый текст + разный nonce = полностью разный результат.
Poly1305 — код аутентификации сообщения (MAC, 128 бит).
Гарантирует целостность: если зашифрованные байты
изменить хоть на 1 бит — расшифровка вернёт ошибку,
а не испорченный текст. Подделка невозможна.
Nonce (одноразовая соль): 24 случайных байта.
Генерируется заново при каждом вызове «Ещё раз».
Именно поэтому результат всегда разный.
Что хранится на сервере: base64(nonce ‖ ciphertext)
K_secret нигде не хранится — уничтожается сразу после шифрования.
Шаг 2
Что хранится на наших серверах
Ниже — всё, что Иван передал на сервер. Больше ничего нет. Попробуйте разгадать секрет, глядя на это.
Данные, которые получил сервер
Зашифрованный текст
—
Ключ для Насти запечатан замком Насти
—
Оригинальный текст — никогда не попадает на сервер
Промежуточные ключи шифрования — существуют только в браузере Ивана в момент создания и сразу уничтожаются
Приватные ключи участников — хранятся только у них в браузере
Если хакер взломает наши серверы — он получит только этот бессмысленный набор символов. Без приватных ключей участников расшифровать что-либо математически невозможно.
Эти данные будут лежать здесь в таком же зашифрованном виде сколько угодно долго. Настя не знает об их существовании — пока не сработает триггер раскрытия. Триггер — пропущенный чек-ин: Иван периодически подтверждает, что он в порядке. Если подтверждение не пришло в срок, сервис запускает процедуру раскрытия. Доверенные люди (если они настроены) дополнительно подтверждают, что это не случайность — и только после этого Настя получает доступ.
Шаг 3
Подтверждение доверенных людей
—
К
Катя подтверждает
✓ Готово
Катя получила свою часть ключа — запечатанную её замком
Расшифровала её своим личным ключом — прямо в браузере. Содержимое секрета она не видит.
Перешифровала часть замком Насти и отправила на сервер
Часть ключа для Насти (от Кати)
—
▶Для любопытных
// В браузере Кати (на её устройстве, без участия сервера):
// 1. Расшифровать свою часть ключа своим приватным ключом:
share = crypto_box_seal_open(enc_share_katya, katya.pub, katya.priv)
// enc_share_katya — зашифрованные данные с сервера
// katya.pub / katya.priv — ключевая пара Кати
// 2. Перешифровать ту же часть замком Насти:
share_for_nastya = crypto_box_seal(share, nastya.pub)
// nastya.pub — публичный ключ Насти (известен всем)
// Результат отправляется на сервер
// Что при этом знает Катя:
// — свою часть ключа (1 из N)
// — не знает другие части, не знает K_trust целиком
// — не видит текст секрета и не знает K_secret
М
Михаил подтверждает
✓ Готово
Михаил получил свою часть ключа — запечатанную его замком
Расшифровал её своим личным ключом — прямо в браузере. Содержимое секрета он не видит.
Перешифровал часть замком Насти и отправил на сервер
Часть ключа для Насти (от Михаила)
—
▶Для любопытных
// В браузере Михаила (на его устройстве, без участия сервера):
// 1. Расшифровать свою часть ключа своим приватным ключом:
share = crypto_box_seal_open(enc_share_mikhail, mikhail.pub, mikhail.priv)
// enc_share_mikhail — зашифрованные данные с сервера
// mikhail.pub / mikhail.priv — ключевая пара Михаила
// 2. Перешифровать ту же часть замком Насти:
share_for_nastya = crypto_box_seal(share, nastya.pub)
// nastya.pub — публичный ключ Насти (известен всем)
// Результат отправляется на сервер
// Что при этом знает Михаил:
// — свою часть ключа (1 из N)
// — не знает другие части, не знает K_trust целиком
// — не видит текст секрета и не знает K_secret
Д
Дмитрий подтверждает
✓ Готово
Дмитрий получил свою часть ключа — запечатанную его замком
Расшифровал её своим личным ключом — прямо в браузере. Содержимое секрета он не видит.
Перешифровал часть замком Насти и отправил на сервер
Часть ключа для Насти (от Дмитрия)
—
—
Шаг 4
Настя открывает секрет
Всё расшифрование происходит в браузере Насти. На сервер расшифрованные данные не уходят ни на каком этапе.
Финальный ключ шифрования (K_secret)
Попробуйте изменить один символ и нажмите «Расшифровать» — ключ станет неверным
Расшифровка не удалась — ключ неверный. Poly1305-MAC обнаружил несоответствие: эти данные не были зашифрованы данным ключом. Даже один изменённый символ — и расшифровать математически невозможно.
Настя видит:
—
—
▶Для любопытных — полная техническая картина расшифрования
—
Справочник
Какие алгоритмы мы используем
Для чего
Алгоритм
Простыми словами
Шифрование секрета
XSalsa20-Poly1305
Из семейства шифров, используемых в WireGuard и TLS 1.3
Шифрование ключей и частей
X25519 Sealed Box
Асимметричное шифрование: запечатать может кто угодно, открыть — только владелец
Хранение ключей в браузере
AES-256-GCM
Военный стандарт, используется в банках и государственных системах
Пароль → ключ шифрования
PBKDF2-SHA256, 100 000 итераций
Замедляет перебор паролей в 100 000 раз
Разделение ключа на части
Shamir's Secret Sharing GF(256)
Математически доказано: одна часть не несёт никакой информации о секрете
Подписи запросов к серверу
Ed25519
Гарантирует что запрос пришёл именно от вас. В реальном сервисе — при каждом обращении к API.
Финальный ключ шифрования
BLAKE2b (crypto_generichash)
Объединяет две части в один ключ. Быстрее SHA-256, такой же надёжный