Как работает наше сквозное шифрование
Última actualización: junio de 2026
Коротко говоря
- Ваши данные шифруются на вашем устройстве и покидают его только в виде нечитаемого шифротекста. Наш сервер хранит исключительно зашифрованные BLOB'ы и не имеет доступа к открытому тексту.
- Все ключи выводятся из 128-битного кода восстановления, который никогда не покидает ваше устройство. Мы его не знаем и не можем восстановить.
- Каждая отдельная запись шифруется собственным ключом и перед расшифровкой проверяется на подлинность (Verify-before-Decrypt с Ed25519).
- E2EE защищает не всё: определённые метаданные (объём данных, время доступа) остаются видны, и скомпрометированное устройство или потерянный код восстановления нельзя спасти криптографией.
Что означает «сквозное» в нашем контексте
Сквозное шифрование означает: открытый текст ваших данных существует только на ваших собственных устройствах. Шифрование и расшифровка происходят исключительно локально. То, что синхронизируется между устройствами и хранится на нашем сервере, всегда уже зашифровано до того, как покидает устройство.
Конкретно: строка данных кодируется на клиенте канонически в JSON, а затем шифруется с помощью AEAD-алгоритма. Сервер получает только непрозрачный байтовый BLOB плюс несколько служебных полей. Он не обладает ни одним из ключей, необходимых для расшифровки. Это архитектурное свойство, а не обещание: поскольку ключи никогда не доходят до сервера, он технически не может прочитать содержимое.
Нажмите на компонент, чтобы узнать больше:
Устройство А (Отправитель)
Данные шифруются локально на устройстве с помощью AES-256-GCM. Код восстановления и производные ключи никогда не покидают устройство.
Как шифруются ваши данные
Мы используем проверенные, стандартизированные компоненты из библиотек @noble/ciphers, @noble/hashes и @noble/curves:
- AES-256-GCM шифрует ваши записи. AES-256-GCM является AEAD-алгоритмом, который одновременно обеспечивает конфиденциальность (никто не читает с) и целостность (манипуляция выявляется). Каждая запись использует случайный 12-байтовый Nonce и 16-байтовый код аутентификации (MAC). Формат: nonce(12) ‖ ciphertext ‖ mac(16).
- XChaCha20-Poly1305 шифрует материал ключей при сопряжении устройств (Key-Wrapping). Это тоже AEAD-алгоритм, но использует более длинный 24-байтовый Nonce, что делает случайные Nonce некритичными.
- AAD (Associated Data) привязывает каждый шифротекст к его метаданным — заголовку, Bucket, UUID записи, ревизии, эпохе ключа, версии схемы и дополненной длине. Эти поля аутентифицированы: если хотя бы одно из них будет изменено, расшифровка не удастся. Пустой AAD строго отклоняется (защита от Confused-Deputy).
- Padding маскирует точный размер: каждая запись дополняется до размера Bucket'а (256, 1024, 4096, 16384 или 65536 байт; выше — кратные 65536). Так размер BLOB'а раскрывает только приблизительный класс, а не точный объём данных.
- Подпись Ed25519 (blob_sig) подписывает каждый BLOB. Перед каждой расшифровкой клиент проверяет эту подпись против авторизованных ключей устройства (Verify-before-Decrypt). Проверка работает строго по RFC-8032 (zip215:false), что исключает подделки через точки низкого порядка. Манипулированные данные сразу выдают ошибку, без тихого отказа.
Ваши ключи остаются на вашем устройстве
Основа всего — 128-битная энтропия восстановления — код восстановления. Из этих 16 случайных байт с помощью HKDF-SHA256 (функция вывода ключей, которая из секрета генерирует много отделённых ключей) детерминированно выводится главный секрет и из него — дополнительные ключи: ID учётной записи, ключ шифрования ключей, ключ аутентификации (authSeed) и маршрутный ключ. Каждый вывод использует собственный версионированный ярлык (например, mypep/master/v1), так что ключи не перекрываются.
На каждую коллекцию приходится собственный ключ коллекции, и на каждую запись и ревизию — собственный ключ записи (rec/<uuid>/<rev>). Этот собственный ключ для каждой пары (Запись, Ревизия) обеспечивает полное разделение ключей: ни один ключ AES-GCM не используется дважды — сильная защита от атак Nonce-Reuse.
Главный секрет намеренно не хранится. Локально хранятся только выведённые значения и собственные ключи подписания/оборачивания устройства — в браузере в IndexedDB.
Нажмите на ключ для подробностей:
Код восстановления
128-битная случайная энтропия. Абсолютный корневой ключ. Создается при регистрации. Никогда не покидает устройство.
Восстановление — возможность и ответственность: весь дерево ключей зависит от кода восстановления. Мы его не сохраняем; лазейка, условное хранение ключей на сервере или серверный сброс архитектурно исключены — не только по политике, но потому что ключи технически никогда не достигают сервера. Это ядро подлинного E2EE: никто, кроме вас — даже мы — не может восстановить ваши данные без кода восстановления или сопряжённого устройства. Если код потерян и больше нет сопряжённого устройства, данные навсегда недоступны.
Что видит наш сервер — и что нет
Сервер хранит только зашифрованные BLOB'ы. Он не видит: содержимое ваших записей, имена ваших коллекций (они появляются только как непрозрачный HMAC-SHA256-Bucket) или ваши ключи.
Честно говоря, он видит некоторые метаданные: непрозрачные значения Bucket'а, ревизии записей, приблизительный размерный класс через Padding, частоту синхронизации, ID устройств (как SHA-256-хэш ключей устройства) и позицию курсора при выравнивании. Из этого можно вывести, что учётная запись активна и примерно сколько и какой объём данных перемещается когда — но не то, о чём это содержательно.
Разрешение конфликтов работает чисто через ревизии (более высокая ревизия выигрывает, «Last-Write-Wins»), потому что сервер не знает содержимое и не может содержательно объединять. Аутентификация выполняется посредством Challenge-Response с Ed25519 и последующим JWT-токеном; список устройств подписывается вашим ключом аутентификации и проверяется исключительно на клиенте.
Несколько устройств (сопряжение)
Новое устройство, например браузер, сопрягается с телефоном через QR-код. QR передаёт открытые ключи устройства браузера напрямую (out-of-band), не через сервер — это защищает обмен ключами от атак Man-in-the-Middle. Телефон упаковывает Identity-Bundle (ключи данных учётной записи, ключ аутентификации, ID учётной записи, маршрутный ключ) с помощью X25519-ECDH и XChaCha20-Poly1305 исключительно для браузера и передаёт его в зашифрованном виде.
После расшифровки браузер показывает отпечаток (SHA-256 ID учётной записи в виде групп в шестнадцатеричной форме). Если он совпадает с отображением на телефоне, гарантируется, что вы получили правильный Bundle. Веб-клиенты не имеют собственного кода восстановления: при потере памяти браузера вы просто повторно сопрягаетесь с телефоном.
Процесс сопряжения
Браузер создает временные ключи и показывает их как QR-код. Смартфон сканирует его камерой. Этот прямой обмен (out-of-band) защищает обмен ключами от атак типа «человек посередине».
Чего это НЕ защищает
E2EE мощно, но не является панацеей. Честно о границах:
- Скомпрометированное устройство: вредоносное ПО в контексте браузера или прямой доступ к IndexedDB может читать локально хранимые ключи и таким образом расшифровать всё. Локальная база данных лежит в открытом виде.
- Потерянный код восстановления: без кода и без сопряжённого устройства пути назад нет. Не существует ротации ключей и механизма резервной копии.
- Метаданные: объёмы данных, размерные классы, шаблоны доступа и временные метки остаются видимы для сервера.
- Фото-BLOB'ы: фотографии адресуются по контенту (
blob_id = sha256(blob)). Кто уже владеет открытым текстом, может проверить, была ли загружена именно эта фотография. - Враждебный сервер: он не может прочитать или подделать содержимое (Verify-before-Decrypt защищает), но он мог бы задержать данные или повторно доставить старые подписанные списки устройств (Replay). Ущерб от такого Replay ограничен: устройство регистрируется идемпотентно, так что переигранный старый список не перезапишет текущее состояние. Новые допустимые данные сервер не может придумать.
- Ключ аутентификации: кто получит ключ аутентификации, может изменить список устройств и повторно аутентифицироваться. Он должен оставаться секретным.
Криптографические компоненты в обзоре
- AES-256-GCM — шифрование записей (AEAD); 12-байтовый Nonce, 16-байтовый MAC.
- XChaCha20-Poly1305 — Key-Wrapping при сопряжении (AEAD); 24-байтовый Nonce, 16-байтовый MAC.
- Argon2id v1.3 — на основе пароля, устойчивая к памяти производная (стандарт: 64 МиБ, 3 итерации, 4 дорожки); доступна для будущих функций на основе пароля, но НЕ для кода восстановления — это уже 128-битная случайная энтропия и не требует Padding памяти.
- HKDF-SHA256 — вывод всех ключей из кода восстановления, отделённый по доменам через версионированные ярлыки; 32-байтовый вывод.
- HMAC-SHA256 — непрозрачная маршрутизация коллекций (расчёт Bucket'а).
- SHA-256 — хеширование для отпечатков, ID устройств и адресации фото.
- Ed25519 (RFC-8032-strict, zip215:false) — подписи для записей, списка устройств и аутентификации.
- X25519-ECDH — обмен ключами при сопряжении устройств.
- Padding-Buckets (256 Б до 65536 Б, выше — кратные 65536) — обфускация размера.
- Длина ключа последовательно 32 байта (256 бит).