Документ честный, а не рекламный: сначала то, что защищено и на каком основании, потом то, что не защищено.
| От чего | Насколько защищены |
|---|---|
| Пассивное прослушивание канала | Да, на уровне современных мессенджеров |
| Активный 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, поэтому их понижение ломает тег.
Состав группы зафиксирован ростером: подмена ключа участника невозможна в принципе, а не обнаруживается постфактум.
Чужой ввод. Пределы на размер кадра проверяются до выделения памяти; имена файлов санитизируются; хеш файла проверяется целиком, и файл появляется в каталоге только после проверки; входящий текст очищается от управляющих последовательностей; поток предложений файлов ограничен.
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 — собственный протокол поверх, и он ни с чем чужим не встречался.
Отдельной команды нет намеренно: ротация ключа в группе — это согласование между людьми, а не техническая операция, и автоматизация создала бы видимость, что главный шаг можно пропустить.
- Переименуйте старый ключ, не удаляя:
mv ~/.p2pchat/id.key ~/.p2pchat/id.key.old - Создайте новый:
python -m p2pchat keygen --nick <ваш ник> - Раздайте новое приглашение:
python -m p2pchat invite --address host:port - Сверьте новый отпечаток голосом с каждым. Смена ключа — ровно то событие, которым маскируется подмена; остальные увидят предупреждение «под этим ником известен другой ключ».
- Каждый делает
/forget <ваш ник>и принимает новое приглашение. - Кто-то один собирает обновлённый ростер и раздаёт:
invite --group.
Резервная копия — просто копия id.key в надёжном месте: файл зашифрован
пассфразой, но пассфразу без него не восстановить, как и его без неё.
Собеседник пришёл с другим ключом под знакомым ником. Соединение
отвергается автоматически. Свяжитесь с человеком по другому каналу. Если он
подтверждает, что переустановил чат, — /forget <ник> и сверка нового
отпечатка голосом. Если не подтверждает — не соединяйтесь.
SAS не совпал. Разорвите соединение и не пользуйтесь этим каналом. Ключ собеседника подменён по дороге либо кто-то сидит между вами.
Ключ утёк. Пересоберите группу без этого ключа и раздайте новый ростер. Переписку, перехваченную ранее, это не расшифрует — за неё отвечает forward secrecy, — но дальнейший доступ закроет.