惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
月光博客
月光博客
AI
AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
CXSECURITY Database RSS Feed - CXSecurity.com
WordPress大学
WordPress大学
GbyAI
GbyAI
L
Lohrmann on Cybersecurity
O
OpenAI News
Schneier on Security
Schneier on Security
P
Palo Alto Networks Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Troy Hunt's Blog
V2EX - 技术
V2EX - 技术
W
WeLiveSecurity
L
LINUX DO - 最新话题
人人都是产品经理
人人都是产品经理
S
Security Affairs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
A
Arctic Wolf
Recorded Future
Recorded Future
Microsoft Security Blog
Microsoft Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
G
GRAHAM CLULEY
N
Netflix TechBlog - Medium
TaoSecurity Blog
TaoSecurity Blog
C
Check Point Blog
Scott Helme
Scott Helme
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
PCI Perspectives
PCI Perspectives
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Vercel News
Vercel News
The Hacker News
The Hacker News
Y
Y Combinator Blog
Latest news
Latest news
SecWiki News
SecWiki News
Hugging Face - Blog
Hugging Face - Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
Recent Commits to openclaw:main
Recent Commits to openclaw:main
C
Cisco Blogs
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
宝玉的分享
宝玉的分享
L
LINUX DO - 热门话题

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Два месяца спустя. Что я понял, пока пытался сделать E2EE-мессенджер
Евгений · 2026-06-24 · via Все публикации подряд на Хабре

Средний

8 мин

191

Два месяца назад я опубликовал статью про свой pet-проект — E2EE-мессенджер.

Честно говоря, я не ожидал, что она вызовет такой интерес. Прилетели комментарии, замечания, вопросы и довольно полезная критика. Где-то меня поправили по делу. Где-то заставили пересмотреть решения, которые мне самому на тот момент казались нормальными.

И, что самое приятное, часть вещей, которые тогда были только в roadmap, за это время удалось реализовать.

Первая часть:

https://habr.com/ru/articles/1030854/

Проект:

https://github.com/vaazhen/chaos-e2ee-messenger

Вообще, за эти два месяца я понял одну простую вещь.

Написать чат оказалось не так сложно.

Написать E2EE-мессенджер — уже сложнее.

А вот понять, почему Signal делали столько лет, оказалось совсем другой историей.

Сначала кажется: ну есть же документация, есть X3DH, есть Double Ratchet, есть WebCrypto, есть статьи, есть спецификации. Осталось просто аккуратно всё собрать.

Примерно в этот момент где-то вдалеке начинает смеяться инженер из Signal.

Сразу оговорюсь: Chaos — это не замена Signal и не “готовый безопасный мессенджер”, а open-source pet-проект, на котором я разбираюсь, как E2EE-системы устроены изнутри.

Зачем я продолжаю этим заниматься (я безработный)

Изначально я хотел просто разобраться, как работают X3DH и Double Ratchet.

Потом стало интересно сделать не абстрактный пример на 200 строк, а что-то похожее на живую систему:

  • backend;

  • frontend;

  • realtime;

  • база данных;

  • доставка сообщений;

  • несколько устройств;

  • observability;

  • desktop-клиент;

  • деплой;

  • безопасность;

  • странные edge cases, которые появляются в самый неподходящий момент.

Постепенно проект перестал быть “чатиком на Spring Boot”.

Он начал превращаться в систему, где любое решение про ключи внезапно влияет на базу данных, WebSocket, UX, preview сообщений, авторизацию, восстановление сессий и даже на то, что можно показывать в списке чатов.

Именно поэтому он до сих пор не надоел.

Как сейчас выглядит проект

Если в первой версии всё выглядело довольно просто, то сейчас проект заметно разросся.

Схема, конечно, сильно упрощённая. В реальности всё чуть более шумно, потому что у любого живого проекта есть свойство постепенно превращаться в маленький дата-центр у тебя на ноутбуке.

Сейчас в проекте есть:

  • React-фронтенд;

  • Electron desktop-приложение;

  • Spring Boot backend;

  • REST API;

  • STOMP/WebSocket для realtime;

  • PostgreSQL для пользователей, устройств, сообщений, envelope'ов, prekeys и вложений;

  • Redis для rate limits, сессий, online status и refresh-токенов;

  • Docker Compose;

  • Prometheus;

  • Grafana;

  • Loki;

  • Caddy;

  • Kubernetes-манифесты.

Но главный вывод здесь не в количестве технологий.

Главный вывод в том, что E2EE — это не отдельный crypto-service и не одна функция в коде. Это архитектурное свойство всей системы.

Если сервер не должен видеть plaintext, это влияет почти на всё.

И именно в этот момент начинаешь понимать, что E2EE — это не функция encrypt(), а ограничение, которое постепенно прорастает через всю систему.

Сервер должен знать как можно меньше

Одна из вещей, которая сильнее всего поменялась за это время, — моё понимание роли сервера.

В обычном мессенджере сервер часто является источником истины почти обо всём:

  • текст сообщения;

  • preview последнего сообщения;

  • поиск;

  • история;

  • статусы;

  • вложения;

  • индексация.

В E2EE-мессенджере сервер должен быть намного тупее.

И это комплимент.

Он должен принимать зашифрованные envelope'ы, хранить ciphertext и доставлять его нужным устройствам. Всё остальное — по возможности мимо него.

Упрощённо доставка выглядит так:

  1. Alice шифрует сообщение локально.

  2. Для каждого устройства Bob создаётся отдельный encrypted envelope.

  3. Сервер сохраняет ciphertext.

  4. Сервер доставляет envelope'ы всем устройствам Bob.

  5. Bob расшифровывает сообщение локально.

  6. Read receipt уходит обратно как отдельное событие.

Сервер при этом не видит текст сообщения.

Но важно: это не значит, что сервер “ничего не знает вообще”. Он всё ещё видит метаданные: кто с кем общается, когда были события, сколько примерно данных передано, какие устройства активны.

И это отдельная большая тема.

Когда [encrypted] — это не баг

Один из забавных моментов был с preview последнего сообщения.

На сервере поле content выглядит примерно так:

[encrypted]

Поначалу это немного раздражает.

Открываешь базу, смотришь на сообщение и видишь не текст, а заглушку. Первая мысль: “Ну всё, опять что-то сломалось”.

А потом приходит понимание: нет, дружище, это как раз оно и работает.

Сервер не должен знать, что находится внутри сообщения.

В обычном приложении preview последнего сообщения — это простой SQL-запрос.

В E2EE-мессенджере всё интереснее. Preview приходится хранить локально на клиенте после расшифровки. Сервер может хранить только безопасную заглушку, потому что иначе он снова начинает знать то, чего знать не должен.

В какой-то момент я поймал себя на мысли, что если очень хочется решить проблему на сервере, сначала стоит задать себе вопрос:

А должен ли сервер вообще знать ответ?

Очень часто правильный ответ — нет.

И после этого архитектура становится неудобнее.

Зато честнее.

Самое большое изменение — я добрался до DH Ratchet

В комментариях к первой статье мне справедливо указали, что тогда настоящего Double Ratchet ещё не было.

Была начальная установка сессии через X3DH и симметрическое обновление message keys, но не хватало важной части — DH Ratchet. А без него не получается нормального break-in recovery.

За эти два месяца я добрался до этой части.

Если объяснять очень грубо, Double Ratchet состоит из двух идей.

Первая — симметрическая цепочка ключей.
Для каждого сообщения используется новый message key, а старые ключи постепенно становятся бесполезными.

Вторая — DH Ratchet.
Когда собеседник отвечает новым DH-ключом, стороны пересчитывают root key и получают новые sending/receiving chains.

Самое важное здесь — break-in recovery.

Если злоумышленник каким-то образом получил текущий chain key, это плохо. Но после нового DH-обмена старое состояние перестаёт помогать расшифровывать новые сообщения.

Не магия. Не абсолютная защита от всего. Но очень важное свойство протокола.

Упрощённо шаг DH Ratchet в коде выглядит так:

async function dhRatchetStep(session, remotePublicKey) {
  // переключаемся на новый публичный DH-ключ собеседника
  session.remoteDhPublicKey = remotePublicKey;

  // обновляем receiving chain
  const receivingSecret = await deriveSharedSecret(
    session.ownDhKeyPair.privateKey,
    remotePublicKey
  );

  const receivingKeys = await deriveRootAndChainKey(
    session.rootKey,
    receivingSecret
  );

  session.rootKey = receivingKeys.rootKey;
  session.receivingChainKey = receivingKeys.chainKey;

  // генерируем новый DH keypair для своей sending chain
  session.ownDhKeyPair = await generateDhKeyPair();

  const sendingSecret = await deriveSharedSecret(
    session.ownDhKeyPair.privateKey,
    remotePublicKey
  );

  const sendingKeys = await deriveRootAndChainKey(
    session.rootKey,
    sendingSecret
  );

  session.rootKey = sendingKeys.rootKey;
  session.sendingChainKey = sendingKeys.chainKey;

  session.sentCount = 0;
  session.receivedCount = 0;
}

На бумаге всё выглядит красиво.

И именно здесь я впервые поймал себя на мысли, что читать спецификацию Signal гораздо проще, чем встроить её в живую систему.

В реальности начинают вылезать неприятные вопросы:

  • что делать с out-of-order сообщениями;

  • сколько skipped message keys можно хранить;

  • что происходит, если одно устройство долго было offline;

  • как не сломать multi-device;

  • как восстановиться после переустановки клиента;

  • где заканчивается удобство и начинается снижение безопасности.

Чем больше я читал спецификацию Signal, тем сильнее начинал уважать людей, которые всё это не просто придумали, а ещё и довели до состояния продукта.

Multi-device ломает простую картинку мира

Когда у пользователя одно устройство, можно притворяться, что всё относительно понятно.

Есть Alice.
Есть Bob.
Есть сессия между ними.

Но потом у Bob появляется телефон, ноутбук и ещё один браузер, который он открыл “на пять минут”, а потом не закрывал полгода.

И простая картинка заканчивается.

Теперь сообщение нужно отправлять не “пользователю Bob”, а каждому устройству Bob отдельно. У каждого устройства свои ключи, prekeys, сессии и envelope'ы.

Сервер при этом превращается в довольно честного курьера:

“Я не знаю, что внутри коробки, но знаю, на какие адреса её отнести”.

И это нормально.

Но multi-device без проверки устройств — это только половина дороги. Технически добавить новое устройство не так сложно. Сложнее доказать пользователю, что это действительно его устройство, а не кто-то тихо подключился к аккаунту.

Именно поэтому в roadmap появились safety numbers и device verification.

Два часа дебага из-за двух стандартов

Ещё одна история, которая хорошо запомнилась.

Клиент подписывает данные через WebCrypto.

Java на сервере должна проверить подпись.

Ключи правильные.
Данные правильные.
Алгоритм правильный.
Всё выглядит так, будто должно работать.

Но подпись не проходит.

После нескольких часов оказалось, что проблема вообще не в “криптографии” в страшном смысле слова.

Проблема была в формате подписи.

WebCrypto возвращает ECDSA-подпись в формате IEEE P1363.

А Java/BouncyCastle в этом месте ожидает ASN.1 DER.

Обе стороны честно делают свою работу.

Просто одна говорит “привет” на одном языке, другая ждёт “hello” на другом, а ты сидишь между ними и думаешь, в какой момент жизнь свернула не туда.

В итоге понадобился конвертер:

byte[] derSignature = convertP1363ToDer(rawSignature);

boolean valid = signature.verify(derSignature);

Код небольшой.

Нервов съел прилично.

И это, наверное, один из самых полезных типов багов. Потому что после него ты лучше понимаешь не только свой код, но и границы между браузером, Java и криптографическими библиотеками.

Что неожиданно оказалось не проблемой

Забавно, но Spring Boot, WebSocket, Docker и даже Kubernetes оказались не самой сложной частью.

Да, у каждого из них есть свои приколы. Kubernetes вообще иногда ведёт себя так, будто не ты деплоишь приложение, а он проводит собеседование.

Но настоящие проблемы начались не там.

Настоящие проблемы начались вокруг доверия, ключей и состояния сессий.

Чем дальше развивается проект, тем меньше времени уходит на “просто написать код” и тем больше — на понимание того, какое поведение вообще должно быть правильным.

Было и стало

Если совсем коротко, проект за два месяца прошёл примерно такой путь:

Было

Стало

X3DH

X3DH + DH Ratchet

Web-клиент

Web + Electron Desktop

Single device

Multi-device

Базовая наблюдаемость

Prometheus + Grafana + Loki

Docker

Docker + Kubernetes

Серверная логика чата

Сервер как маршрутизатор encrypted envelope'ов

Но для меня главное изменение не в этом списке.

Главное изменение — поменялась модель мышления.

В начале проекта я думал примерно так:

Как мне отправить сообщение?

Теперь вопрос звучит иначе:

Какие данные система имеет право знать, чтобы доставить сообщение?

Это совсем другой уровень неприятности.

И, кажется, именно там начинается настоящая инженерия E2EE.

Немного про AI

Да, часть бойлерплейта, тестов и рутинных кусков помогали писать Claude и ChatGPT. Но чем дальше развивался проект, тем больше выяснялось, что вопросы вокруг доверия, восстановления сессий, компрометации устройств, forward secrecy и архитектурных компромиссов всё равно приходится решать самому. В E2EE понимание важнее генерации кода.

Что всё ещё слабое место

Чтобы не создавать лишних иллюзий, вот что я сам пока считаю незавершённым.

Web-доставка frontend-кода. Даже если backend не видит plaintext, web-клиент всё равно загружается с сервера. При определённой модели угроз сервер или инфраструктура доставки могут попытаться подменить клиентский код. Desktop-приложение частично снижает этот риск, но не убирает его полностью.

Safety numbers и device verification. Пока нет удобного механизма, который позволяет пользователям проверить отпечатки ключей и убедиться, что новое устройство действительно принадлежит собеседнику. Без этого multi-device остаётся неполным с точки зрения UX доверия.

Metadata leakage. Сервер не видит текст сообщений, но всё ещё видит часть метаданных: когда были события, какие устройства подключены, кому нужно доставить envelope, какие сессии активны. Это отдельный пласт проблем, который нельзя честно игнорировать.

Security review. Проект не проходил внешний криптографический аудит. А когда речь идёт про E2EE, это важно проговаривать прямо. Сам себе security review — это примерно как самому себе стоматолог: теоретически интересно, практически лучше не надо.

Что дальше

Сейчас в планах:

  • Safety Numbers;

  • Device Verification;

  • Push Notifications;

  • Android-клиент;

  • внешний security review;

  • дальнейшая работа над multi-device;

  • улучшение восстановления сессий.

За эти два месяца проект стал больше.

Но главный вывод оказался не про количество кода, не про Docker, не про Kubernetes и даже не про Double Ratchet.

Главный вывод в том, что сложность E2EE начинается не там, где появляется AES-GCM или X25519.

Она начинается там, где нужно решить:

  • кому можно доверять;

  • какие данные сервер имеет право знать;

  • что делать с несколькими устройствами;

  • как жить после компрометации;

  • где безопасность конфликтует с удобством;

  • как не сломать всё это красивым UX.

Наверное, именно поэтому большие мессенджеры развиваются годами.

А мне пока просто интересно продолжать копать.

И, кажется, чем глубже копаешь, тем больше начинаешь понимать, почему у Signal до сих пор выходят статьи, спецификации и новые версии протокола. Потому что в таких системах вопросы почему-то никогда не заканчиваются.

И да, спасибо всем, кто критиковал первую статью.

Многие вещи, которые сейчас есть в проекте, появились именно благодаря комментариям.

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

В прошлый раз это уже помогло.