Как RMBL Chat хранит ваши сообщения в секрете
Привет! Это первая статья в блоге RMBL на DTF.
Разберём, что именно происходит с сообщением между моментом, когда пользователь нажимает «Отправить», и моментом, когда текст появляется на экране собеседника.
Нас будут интересовать четыре вещи:
- как два устройства обмениваются публичными ключами;
- как они вычисляют общий секрет, не передавая его через сервер;
- как сообщение превращается в набор нечитаемых данных;
- и как убедиться, что публичный ключ собеседника никто не подменил по дороге.
Текст сообщения покидает устройство уже в зашифрованном виде, а приватные ключи переписки вообще не должны покидать устройства пользователей.
Начнём с того, как это устроено.
Часть 1. Что вообще нужно для надёжного шифрования
В RMBL мы не пытались изобретать собственную криптографию. Для обмена ключами используются давно известные и хорошо изученные криптографические конструкции, основанные на идее Diffie–Hellman.
Идея была опубликована ещё в 1976 году Уитфилдом Диффи и Мартином Хеллманом, а важную роль в появлении публичной криптографии сыграли работы Ральфа Меркла.
Сегодня современные варианты этой идеи используются во множестве защищённых протоколов и мессенджеров.
Суть можно сформулировать так:
Два устройства могут независимо вычислить один и тот же секрет, при этом никогда не передавая сам этот секрет друг другу.
Зачем это нужно?
Представим, что Алиса и Максим хотят шифровать переписку с помощью секретного ключа. Можно было бы просто создать ключ на устройстве Алисы и отправить его Максиму. Но тогда возникает очевидный вопрос: а что, если кто-то перехватит этот ключ во время передачи?
Поэтому вместо одного ключа у каждого участника появляется пара:
Private key (приватный ключ) и Public key (публичный ключ).
Приватный ключ всегда остаётся на устройстве. Публичный ключ можно передавать другим людям и хранить на сервере.
При этом публичный ключ математически связан с приватным, но восстановить приватный ключ из публичного за разумное время считается вычислительно невозможным.
Самое интересное начинается дальше.
Устройство Алисы берёт: PRIVATE KEY ALICE + PUBLIC KEY MAKSIM
Устройство Максима берёт: PRIVATE KEY MAKSIM + PUBLIC KEY ALICE
И в результате оба вычисляют один и тот же общий секрет. При этом сам общий секрет между устройствами не передавался. В RMBL Chat для этой части используется криптография на базе Curve25519.
Теперь посмотрим, как это выглядит непосредственно в RMBL.
Часть 2. Как RMBL обменивается ключами
В RMBL Chat нельзя просто начать отправлять сообщения случайному пользователю. Сначала пользователи должны добавить друг друга в контакты.
Например: @username1 хочет начать общение с @username2. Пользователь @username2 должен подтвердить, что действительно хочет начать эту переписку. Именно во время этого процесса происходит первоначальный обмен публичными ключами.
Шаг 1. Device 1 создаёт пару ключей
Перед отправкой заявки устройство пользователя @username1 генерирует отдельную пару ключей для этой переписки: PRIVATE KEY 1 + PUBLIC KEY 1
Для криптографических операций RMBL Chat приложение использует NaCl. PUBLIC KEY 1 вычисляется из PRIVATE KEY 1 с использованием Curve25519. При этом восстановить приватный ключ по публичному невозможно. После генерации ключи сохраняются локально в защищённом хранилище устройства.
Самое важное здесь: PRIVATE KEY 1 не отправляется на сервер RMBL.
Он не передаётся через WebSocket. Не кладётся в push-уведомление. Не отправляется второму пользователю. Он остаётся на Device 1.
Если пользователь потеряет доступ к защищённому локальному хранилищу (например, удалит приложение вместе с его данными), сервер RMBL не сможет просто вернуть этот приватный ключ обратно.
Потому что у сервера его нет.
Шаг 2. На сервер отправляется публичный ключ
Теперь Device 1 отправляет заявку на добавление в контакты. На сервер уходят:
- @username2
- PUBLIC KEY 1
PRIVATE KEY 1 в запросе отсутствует.
После этого сервер RMBL создаёт новую переписку. На этом этапе её криптографическое состояние можно очень условно представить так:
То есть публичный ключ первого участника уже есть, а второго eщё нет. После создания заявки сервер отправляет push-уведомление пользователю @username2.
Важно отметить, что push в данном случае используется как сигнал, а не как канал передачи чувствительных данных. В него не нужно помещать содержимое сообщения, приватные ключи или другие данные, которые приложение может получить непосредственно с RMBL сервера.Даже публичные ключи через push-уведомления не передаются.
Шаг 3. Device 2 подтверждает заявку
Пользователь @username2 принимает заявку в контакты. После этого Device 2 создаёт собственную пару ключей для этой же переписки: PRIVATE KEY 2 и PUBLIC KEY 2
PRIVATE KEY 2 сохраняется в защищённом хранилище Device 2. И снова действует то же правило: PRIVATE KEY 2 никогда не отправляется на сервер. На сервер отправляется только PUBLIC KEY 2.
Теперь запись переписки выглядит примерно так:
После этого сервер уведомляет @username1 о том, что заявка была принята. На этом первоначальный обмен публичными ключами завершён.
- У Device 1 есть PUBLIC KEY 2;
- У Device 2 есть PUBLIC KEY 1.
При этом PRIVATE KEY 1 всё ещё находится только на первом устройстве, а PRIVATE KEY 2 только на втором.
Часть 3. Что происходит при отправке сообщения
Теперь @username1 хочет отправить обычное сообщение:
Hi, how are you?
Пользователь набирает текст, но после нажатия на кнопку «Отправить» текст не уходит сразу на сервер, так как сначала его нужно зашифровать.
Шаг 1. Device 1 вычисляет общий секрет
На Device 1 уже есть собственный PRIVATE KEY 1 и доступен для получения от сервера PUBLIC KEY 2, где оба ключа сгенерированы на двух устройствах и используются только для шифрования обмена данными между пользователями @username1 и @username2 на стороне Device 1.
На их основе с помощью Curve25519 вычисляется общий криптографический секрет.
Упрощённо: PRIVATE KEY 1 + PUBLIC KEY 2 = SHARED SECRET
А общим он называется потому что на Device 2 можно получить такой же SHARED SECRET но уже с помощью пары ключей PRIVATE KEY 2 и PUBLIC KEY 1
Таким образом и реализуется возможность использования идентичного ключа шифрования без его передачи по сети.
Шаг 2. Сообщение превращается в ciphertext
Теперь полученный криптографический материал можно использовать для защиты сообщения. NaCl использует для этого несколько криптографических примитивов. За шифрование отвечает XSalsa20.
Обычный читаемый текст:
Hi, how are you?
превращается в набор данных, который без правильного ключа не имеет полезного смысла.
a13bjh934baklh fd4482bdkq12 3lajehd204
В реальности ciphertext, конечно, представляет собой бинарные данные. Строка выше нужна только для иллюстрации. Задача криптографии — сделать так, чтобы из ciphertext нельзя было получить plaintext без соответствующего секретного ключа.
Шаг 3. Недостаточно просто скрыть текст
Представим, что злоумышленник не может прочитать зашифрованное сообщение. Но может ли он изменить его? Например, заменить несколько байтов в ciphertext и отправить изменённую версию получателю? Чтобы обнаруживать такие изменения, используется код Poly1305. Он добавляет к защищённым данным криптографическую аутентификацию.
В результате принимающее устройство может проверить:
- действительно ли данные соответствуют ожидаемому ключу;
- не были ли они изменены;
- не повредились ли они во время передачи.
Если ciphertext изменить, проверка не пройдёт. То есть решаются сразу две разные задачи.
- Конфиденциальность. Посторонний не должен прочитать содержимое.
- Целостность и аутентичность. Посторонний не должен иметь возможности незаметно изменить защищённые данные.
Шаг 4. Зачем нужен nonce
Есть ещё одна важная часть схемы, и это nonce, что можно трактовать как «число, используемое один раз». Само по себе это числе не является секретом. Но при использовании одного и того же ключа nonce не должен повторяться.
Зачем это нужно?
Предположим, пользователь дважды отправляет:
Hello
Мы не хотим, чтобы оба сообщения после шифрования выглядели одинаково. С новым nonce первый результат может выглядеть так:
7ca82d1f...
а второй:
e41b903a...
Исходный текст одинаковый. Зашифрованные данные разные. Это убирает очевидные повторяющиеся паттерны в ciphertext.
Часть 4. Что видит сервер RMBL
После того как сообщение зашифровано, Device 1 отправляет его на сервер. На этом этапе вместо понятного текста сообщения, сервер получает ciphertext и связанные с ним технические данные, необходимые для последующей проверки и расшифровки на устройстве получателя.
Сервер сохраняет зашифрованное сообщение в переписке.
Если Device 2 подключён к RMBL Server через WebSocket, сообщение можно сразу передать ему. Если устройство офлайн, сервер может отправить push-уведомление о том, что есть новые непрочитанные сообщения.
Но сам текст сообщения в push помещать не требуется. Push здесь снова выполняет роль сигнала: «Открой соединение с RMBL, там есть новые данные».
Часть 5. Как Device 2 получает исходный текст
Device 2 получает зашифрованное сообщение. Имея PRIVATE KEY 2 и имея возможность загрузить PUBLIC KEY 1, можно вычислить такой же SHARED KEY, как тот, который использовался для шифрования сообщения на Device 1. Перед расшифровкой производится проверка аутентификации аутентичности с помощью Poly1305.
Значит, сервер вообще ничего не знает?
Не совсем. Здесь важно разделять две вещи: содержимое сообщений и метаданные. Чтобы доставить данные, серверу всё равно нужна служебная информация.
Например:
- какие аккаунты участвуют в переписке;
- какому пользователю нужно доставить зашифрованные данные;
- есть ли сообщения, ожидающие устройство;
- техническая информация, связанная с доставкой.
Это метаданные, а вот содержимое самого сообщения существует на сервере в зашифрованном виде.То есть сервер может участвовать в доставке переписки, не имея необходимости знать, что именно пользователи друг другу написали.
Но здесь появляется ещё одна проблема
До этого момента мы предполагали, что PUBLIC KEY 2, который получил @username1, действительно принадлежит @username2. И наоборот PUBLIC KEY 1 действительно принадлежит @username1.
Но что произойдёт, если кто-то сумеет подменить публичные ключи? Например, вместо ключа реального собеседника Device 1 каким-то образом получит другой ключ.Тогда можно попытаться встать между двумя пользователями и построить отдельное защищённое соединение с каждым из них.
Это классическая Man-in-the-Middle attack, или MITM.
Само шифрование при этом может продолжать идеально работать. Проблема в другом: пользователь шифрует данные не для того ключа, для которого думает. Поэтому недостаточно просто иметь сильные алгоритмы шифрования. Нужно ещё уметь проверить: а тот ли публичный ключ мы получили?
Четыре эмоджи против MITM
Для этого в RMBL есть визуальная проверка криптографической идентичности переписки. Если открыть профиль собеседника из шапки окна с перепиской, там можно увидеть четыре security emoji, сформированные локально (на устройстве) на основе SHARED KEY из этой переписки.
Например:
🦊 🌊 🚀 🍋
У второго участника переписки отображается соответствующий fingerprint.
Пользователи могут сравнить эти четыре эмоджи между собой. Лучше всего делать это через канал, которому они уже доверяют:
- лично;
- во время звонка;
- через другой заранее известный канал связи.
Если fingerprint совпадает, оба пользователя видят одну и ту же криптографическую идентичность переписки.
Если эмоджи неожиданно различаются — ключи отличаются, и такой диалог лучше не считать проверенным, пока не станет понятно, почему это произошло.
По сути, четыре эмоджи — это более удобное для человека представление того, что традиционно могло бы выглядеть как длинный fingerprint:
8F:41:C2:0A:77:3E:...
Человеку сложно заметить ошибку в десятках символов. А сравнить 4 emoji можно быстро и легко.
За этими четырьмя эмоджи скрывается отдельная интересная тема: как построить fingerprint, какие данные в него включать и почему его проверка позволяет обнаруживать попытку подмены ключей.
Об этом расскажем в одной из следующих статей.
Мобильное приложение RMBL Chat доступно для скачивания в App Store и Play Market. Все функции в приложении полностью бесплатны.
Для того, чтобы пользоваться приложением, требуется запуск и поддержание работы RMBL Server для вашей компании друзей или семьи. Бесплатно установить сервер можно по заявке на лэндинге https://rmbl.chat/ru
Требования:
- VPS на Ubuntu, 2 ГБ ОЗУ
- S3 (AWS recommended)
- Домен или поддомен
- Ключ для доступа к скачиванию серверного приложения (необходимо отправить заявку через форму на сайте https://rmbl.chat/ru)