В интернете нулевых любой школьник мог поднять P2P-хаб на домашнем компьютере. Не было пользовательских соглашений на 50 страниц. Ты сам писал правила, сам модерировал чат и сам решал, кого пускать, а кого банить.
Ностальгируя, мне захотелось переизобрести P2P-хабы прошлого прямо в браузере. Без регистрации по SMS. Без возни с сервером. С нормальной безопасностью.
Предлагаю вам пятничный вайбкод челлендж, у кого получится самая красивая реализация безопасного одностраничного мессенджера. Доставайте свой ClaudCode, Cursor, Jules, Codex или Clowdbot.
Под катом опишу детальнее идею, чтобы задать контекст для вашей AI команды.
План: Начать с текстового чата, по желанию, можно добавить обмен файлами и видео звонки. Чтобы это работало безопасно и безотказно “даже на парковке”.
Кажется, что трудно придумать что-то проще статического файла index.html.
Не нужно ставить зависимости. Открыл файл друга — клиент доставлен.
Любой может открыть файл в Блокноте, проверить его и изменить под себя.
Работает локально, с флешки, хоть с дискеты.
Это идеальный формат доставки. Но есть проблема: чтобы два браузера начали общаться, они должны найти друг друга.
Жаль, что мы не можем крикнуть на всю локальную сеть, но мы можем оставить записку на "общедоступном ресурсе", что мы тут. Используя внешний публичный сервер, который уже работает и доступен.
Для WebRTC (браузер-браузер) он тоже нужен для начального рукопожатия.
В качестве общедоступного ресурса можем использовать публичные демо сервера крупных мессадж брокеров. Они созданы для IoT, лёгкие и быстрые. Например, это: HiveMQ (broker.hivemq.com), EMQX (broker.emqx.io), Mosquitto (test.mosquitto.org) или NATS Demo (demo.nats.io). Все они бесплатны и работают по WSS.
Плюс, что не нужно поднимать свой сервер. Работает из коробки. Минус, что нужен интернет для рукопожатия (хотя в локальной сети можно поднять свой брокер одной командой).
Таким образом, у нас есть два режима работы: Internet и LAN.
Использование брокеров (MQTT или NATS) позволяет нам строить общение вокруг топиков (каналов). Топик — это просто строка-адрес, например my-chat/room-1/messages. Это позволяет строить логику маршрутизации, не написав ни строчки кода.
Важно: это не доверенные сервисы. Это транспорт. Без шифрования туда нельзя ничего отправлять.
Мы подошли к главному: как сделать безопасный чат, если транспорт публичный.
Нам нужен E2EE (End-to-End Encryption). Возьмем “золотой стандарт”, на котором держится весь HTTPS и SSH: ECDH (Elliptic Curve Diffie-Hellman) для обмена ключами и AES-GCM для шифрования данных.
Приятно, что нам не нужны сторонние библиотеки. В современном браузере есть Web Crypto API — это нативные C++ и аппаратно ускоренные криптофункции.
У нас нет удостоверяющего центра (CA), как в HTTPS. Поэтому, нам нужно защититься от атаки “человек посередине” (MITM). В мире SSH это решается моделью TOFU (Trust On First Use).
Вспомните, как вы впервые подключаетесь к серверу по SSH:
Клиент запоминает ключ сервера в known_hosts. И если при следующем подключении ключ изменится, то клиент заблокирует соединение с криком WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!.
Давайте спроектируем такую же модель в браузере.
Абстракции безопасности:
IdentityKey (Паспорт): Долгосрочная пара public + private ключей. Наш цифровой паспорт пользователя. Генерируется один раз при первом запуске и сохраняется при обновлении страницы браузера.
IdentityId (Адрес): Уникальный идентификатор пользователя. Вычисляется как SHA-256 хэш от публичного ключа. Его нельзя подделать, не имея приватного ключа.
IdentityMeta (Визитка): JSON метаданные нашего пользователя, вроде { "username": "Neo", "avatar": "..." }, они не влияют на криптографию.
Для проверки собеседника мы будем использовать SAS (Short Authentication String) — визуальный emoji (🥑🚗🔥🗿) отпечаток. Его мы можем получить из IdentityKey, по аналогии с Telegram, WhatsApp и другими безопасными мессенджерами, чтобы визуально подтвердить личность собеседника.
Но сам по себе “паспорт” не будет шифровать сообщения. Он нужен для двух вещей:
Подпись - чтобы доказывать, что сообщение отправил именно владелец этого ID.
TOFU - чтобы подтвердить, что ID собеседника не изменился с прошлого разговора.
По аналогии с SSH, для шифрования сообщений между пользователями, безопаснее использовать одноразовые сессионные ключи, которые генерируем на каждую сессию общения между пользователями.
Схема рукопожатия:
В начале P2P взаимодействия, генерируется временная пара ключей.
Временный ключ подписывается своим вечным IdentityKey.
Он отправляется собеседнику.
Собеседник проверяет подпись (защита от подмены на лету) и вычисляет общий AES-ключ SharedSecret.
Это даёт практический эффект:
компрометация одного сеанса не раскрывает другие;
можно ротировать ключи/делать новые сессии для улучшения безопасности;
если украдут "Паспорт" (IdentityKey) в будущем, расшифровать старые переписки не получится;
На текущий момент, мы научились отличать доверенных/недоверенных пользователей и можем безопасно взаимодействовать peer-to-peer. Но как быть с чатами? В чатах больше чем один получатель.
Мы не можем шифровать каждое сообщение в чате отдельно для каждого получателя — это забьет весь трафик. Вместо этого мы будем использовать SenderKey (ключ отправителя).
У каждого участника будет свой SenderKey (симметричный AES ключ). Это его “личный микрофон”.
Каждое сообщение в общем чате будет зашифровано своим SenderKey. Чтобы другие могли меня услышать, я должен передать им копию моего SenderKey через защищенный 1-на-1 канал (который мы построили ранее).
Сквозной сценарий работы клиента: регистрация → рукопожатие → обмен ключами комнаты → отправка/получение сообщений.
Чтобы не перегружать статью протоколом и кодом, я вынес в gist спецификацию и набор общих абстракций для прототипа (классы и структуры данных, на которые можно опереться при реализации): Github Gist
Это удобно, если вы генерируете прототип через LLM, чтобы можно было сравнивать разные реализации участников.
Моя реализация (WIP): здесь будет ссылка за выходные.
Для участия в челлендже, отправляйте ссылки на результат в комментах к этому посту, пишите используемые модели и подход (можно ссылку на промт).
Все участники получат +1 в карту (и на хабре тоже). Победителя определим по итогам оценок комментов! Участие свободное. Погнали.
Источник


