Skip to content

Security: TheIIIrd/round-table-chat

Security

docs/security.md

Безопасность

Документ честный, а не рекламный: сначала то, что защищено и на каком основании, потом то, что не защищено.

Коротко, для решения «подходит ли мне это»

От чего Насколько защищены
Пассивное прослушивание канала Да, на уровне современных мессенджеров
Активный MITM Да — при условии, что вы сверили SAS голосом
Кража ключа задним числом Да: эфемерные ключи на сессию
Кража ключа посреди сессии Да, после ближайшей ротации (≤15 минут)
Доступ к выключенному диску Да: ключ под Argon2id
Вредный участник внутри группы Частично: файлы и бот прикрыты, текст нет
Анализ трафика Нет
Целевая атака с ресурсами Не проектировалось и не оценивалось

Главное: самописная криптография без стороннего аудита остаётся самописной криптографией. Для круга друзей — добротная вещь. Для человека, которому угрожает конкретный противник, — нет; там Signal.

На чём держится защита

Хендшейк — Noise_XX_25519_ChaChaPoly_BLAKE2s. Реализация побайтово совпала с официальными тест-векторами Noise, то есть подтверждена независимо, а не только собственными тестами. Паттерн XX передаёт статические ключи уже внутри шифрования, поэтому пассивный наблюдатель не видит, кто с кем разговаривает.

Привязка контекста. В prologue входят версия протокола и идентификатор группы. Расхождение ломает хендшейк сразу, а не проявляется позже.

Транспорт. Тип кадра и счётчик сообщений идут в associated data: подмена типа или пропуск сообщения ломают проверку тега. Счётчик nonce не переиспользуется, при неудачной расшифровке не сдвигается.

Ротация ключей — каждые 1000 сообщений или 15 минут стороны обмениваются новыми эфемерными ключами и подмешивают общий секрет в цепочку. Это даёт post-compromise security: Rekey() из спецификации Noise его не даёт, там только прокрутка ключа хеш-функцией.

Ключ на диске — Argon2id, 256 МиБ, 3 прохода, 4 полосы. Параметры входят в associated data, поэтому их понижение ломает тег.

Состав группы зафиксирован ростером: подмена ключа участника невозможна в принципе, а не обнаруживается постфактум.

Чужой ввод. Пределы на размер кадра проверяются до выделения памяти; имена файлов санитизируются; хеш файла проверяется целиком, и файл появляется в каталоге только после проверки; входящий текст очищается от управляющих последовательностей; поток предложений файлов ограничен.

Сверка SAS — обязательный шаг

Noise_XX доказывает, что у собеседника есть приватный ключ к предъявленному публичному. Он не доказывает, что этот ключ принадлежит нужному человеку: атакующий в середине проводит два честных хендшейка и ретранслирует переписку.

Единственное лекарство — сравнить по независимому каналу значение, которое совпадёт только при отсутствии посредника:

→ bob на связи
  SAS: 05476 43835 20913 77042 67354 88114
  Сверьте код голосом и введите /verify bob

Прочитайте код собеседнику голосом — не в этом же чате и не в мессенджере, который может быть под контролем. Совпал — /verify bob. Пока не сверено, каждое сообщение помечается ? перед ником.

Тридцать цифр — около ста бит; подобрать пару ключей с совпадающим кодом невозможно. Проверка того, что схема работает, есть в тестах: Мэллори проводит два честных хендшейка и получает у сторон разные коды.

Что не защищено

Метаданные. Кто с кем соединён, когда и сколько — видно наблюдателю целиком. Длины сообщений не выравниваются, поэтому по трафику читается ритм разговора и длина реплик. Против анализа трафика проект не защищён никак.

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

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

Ключи в памяти Python не затираются. bytes неизменяемы, сборщик мусора копирует объекты, страница уходит в swap. Против доступа к работающей машине или к дампу памяти защиты нет. Core dumps не отключаются.

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

Приглашение — это TOFU. Оно избавляет от ручного копирования ключей, но не от их проверки: подменённое по дороге приглашение даёт безупречно защищённое соединение не с тем человеком.

Что проверено и чем

Свойство Чем подтверждено
Совместимость хендшейка со спецификацией Официальные вектора Noise, побайтовое совпадение
Стойкость к MITM даёт разные SAS Тест с двумя честными хендшейками
Подделка, повтор, подмена типа отвергаются Тесты транспорта
Потеря сообщения обнаруживается Тест
Кадр из прошлой эпохи ключей не проходит Тест
Ключ вне ростера не пускается Тест на реальных сокетах
Обход пути в имени файла Генеративный тест
ANSI из чужого сообщения не управляет терминалом Тест
Разборщики не падают на мусоре Генеративные тесты

Чего не подтверждено: живой интероп с чужой реализацией Noise. Вектора покрывают паттерн XX, но кадрирование, привязка заголовка и ротация свежим DH — собственный протокол поверх, и он ни с чем чужим не встречался.

Смена долговременного ключа

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

  1. Переименуйте старый ключ, не удаляя: mv ~/.p2pchat/id.key ~/.p2pchat/id.key.old
  2. Создайте новый: python -m p2pchat keygen --nick <ваш ник>
  3. Раздайте новое приглашение: python -m p2pchat invite --address host:port
  4. Сверьте новый отпечаток голосом с каждым. Смена ключа — ровно то событие, которым маскируется подмена; остальные увидят предупреждение «под этим ником известен другой ключ».
  5. Каждый делает /forget <ваш ник> и принимает новое приглашение.
  6. Кто-то один собирает обновлённый ростер и раздаёт: invite --group.

Резервная копия — просто копия id.key в надёжном месте: файл зашифрован пассфразой, но пассфразу без него не восстановить, как и его без неё.

Что делать при подозрении

Собеседник пришёл с другим ключом под знакомым ником. Соединение отвергается автоматически. Свяжитесь с человеком по другому каналу. Если он подтверждает, что переустановил чат, — /forget <ник> и сверка нового отпечатка голосом. Если не подтверждает — не соединяйтесь.

SAS не совпал. Разорвите соединение и не пользуйтесь этим каналом. Ключ собеседника подменён по дороге либо кто-то сидит между вами.

Ключ утёк. Пересоберите группу без этого ключа и раздайте новый ростер. Переписку, перехваченную ранее, это не расшифрует — за неё отвечает forward secrecy, — но дальнейший доступ закроет.

There aren't any published security advisories