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



Результатом этой работы предполагается создание работающего клиент-сервера, пригодного для review разработчиками (то есть немного кода на высокоуровневом языке), достаточно производительного, чтобы использоваться в промышленных условиях, имеющего высокий порог безопасности: GoVPN.

Чем не устраивает уже масса известных имеющихся решений в виде SSH (он может работать с TUN/TAP устройствами и быть использованным для VPN), TLS, OpenVPN или IPsec? Сложностью как протокола, так и кода. А отсюда и сложность review, под вопросом безопасность. Зависимость от организаций США, диктующих, какие алгоритмы могут быть использованы. Только SSH из коробки предлагает такие быстрые, безопасные, независимые от спецслужб алгоритмы как ChaCha20, Poly1305 и Curve25519. TLS-библиотеки и протокол себя массово показали с плохой стороны из-за своей сложности. OpenVPN, когда не использует PSK (pre-shared key – заранее известный обеими сторонам ключ), то тоже зависит от TLS со всеми вытекающими.

Транспортный протокол


Для решения задачи создания просто VPN современные ОС предлагают такую удобную штуку, как TUN/TAP: виртуальные сетевые интерфейсы. Очень просто написать клиент-сервер приложение, читающее frame-ы из интерфейса, заворачивающие их в UDP-пакеты и отсылающие удалённой стороне. Но нам хочется обеспечить конфиденциальность передаваемых данных, аутентификацию обеими сторон соединения (убедиться, что они выдают себя за того, кого мы и ожидаем).

Весь протокол глобально состоит из двух частей: процесса рукопожатия (handshake) и транспорта. Рукопожатие будем пока считать чёрным ящиком на выходе из которого мы получим: IP-адрес с портом противоположной стороны для отправки UDP-пакетов и общий симметричный ключ шифрования. С рукопожатия начинается любое общение, и аутентификацию сторон достаточно произвести только на этом этапе.

Транспорт отвечает за шифрованную (обеспечивающую конфиденциальность полезной нагрузки) передачу сообщений и их приёмку с дешифрованием. В качестве алгоритма/функции шифрования выбираем Salsa20 (или вариант этого алгоритма ChaCha20). На данный момент он имеет очень хорошую криптографическую безопасность (фатальных недостатков в криптоанализах не выявлено), высочайшую производительность и простоту реализации в коде. Это потоковый шифр: то есть можно считать, что результат его работы – это псевдослучайная последовательность байт, которые необходимо XOR-ить с данными, требующими шифрования.

С потоковыми шифрами стоит быть очень аккуратным. В отличии от блочных, их гораздо сложнее анализировать (поэтому популярные сильные шифры сейчас все блочные), и если хотя бы где-то ключ (соответственно и псевдослучайный выход этой функции) использовался дважды, то это моментально приводит к возможности лёгкого дешифрования ранее перехваченных сообщений этого ключа. Однако есть и приятные простые стороны: размер открытого текста равен по длине размеру зашифрованного, не надо размышлять о дополнениях (до кратности размера шифроблока), о разнообразных режимах шифрования (CBC, ECB, CTR и прочие).

Salsa20 на вход принимает кроме ключа с данными ещё и nonce: число, которое должно быть использовано только один раз для заданного ключа. Использование дважды фатально. В качестве nonce мы используем простой, постоянно инкрементирующийся счётчик. Каждый приходящий пакет его увеличивает. Это требует хранить состояние транспортного соединения на обеих сторонах. В качестве альтернативы можно было бы использовать генератор псевдослучайных чисел (PRNG) и обращаться к нему каждый раз, но это гораздо медленнее и найти качественный (который за время работы шифра с одним ключом гарантированно не выплюнет одно и то же число) PRNG проблематично. Принимающая сторона для дешифрования должна также знать этот nonce. Если бы пакеты не терялись и доходили гарантированно в заданном порядке, то достаточно просто хранить состояние nonce счётчика противоположной стороны и инкрементировать его с приходом пакета. Но UDP-пакеты теряются и поэтому к каждому пакету в начале мы прописываем значение nonce.

Это уже работоспособный протокол, обеспечивающий конфиденциальность передачи данных, однако, он уязвим к атакам, присущим всем потоковым шифрам: шифротекст можно осознанно изменить и сделать требуемую подмену открытого текста после дешифрования. Для их предотвращения необходимо аутентифицировать передаваемые пакеты данных: подтверждать и убеждаться в том, что это именно тот набор данных, который и был передан. Как правило, применяют коды аутентичности сообщений (MAC – message authentication code). MAC на входе принимает ключ и данные, которые необходимо аутентифицировать, а на выходе – так называемый тэг, прикрепляемый к сообщению при передаче.

В качестве MAC мы выбираем Poly1305. Как и Salsa20 от того же автора, он имеет высочайший потолок безопасности, производительность и простоту реализации. Poly1305 имеет разный API в разных библиотеках, но в простейшем случае можно предполагать, что его ключи одноразовые и один ключ используется для аутентификации только одного сообщения. В качестве ключа можно иметь какую-то секретную (известную только двум сторонам) часть с прикреплённым к ней счётчиком. Но уже нормальной практикой стало генерирование ключа аутентификации для каждого сообщения на основе выхода Salsa20 PRNG этого пакета. То есть в момент шифрования пакета, мы берём первые 256 бит выхода Salsa20 и используем их в качестве ключа аутентификации Poly1305. Для шифрования их, конечно же, уже не используем. Так как внутреннее состояние Salsa20 это 512 бит, то на всякий пожарный игнорируем и следующие 256 бит выхода. Да, это потеря производительности, но незначительная, и дающая простой (а значит, потенциально более надёжный в плане безопасности) код. Это уже вовсю применяемая практика в SSH и TLS-протоколах.

Таким образом мы получаем аутентифицированный режим шифрования, который действительно может применяться на практике. Единственное, что чуть-чуть дополним, так это обфусцирование/scrambling передаваемого nonce. С точки зрения безопасности нареканий к нему нет, но мы смотрим на то, чтобы в идеале наш протокол был неотличим от шума, от случайных данных, чтобы усложнить жизнь всё достающим DPI-системам анализа трафика.

Nonce это 64 бит блок данных. В идеале можно просто зашифровать его, превратив в подобие шума. У нас уже есть Salsa20-шифр, однако, он представляет собой PRF-функцию (pseudorandom function – псевдослучайная функция), а нам хочется иметь PRP (pseudorandom permutation – псевдослучайные перестановки): просто перемешать биты. Сделать PRP из PRF можно и Luby-Rackoff доказали это. Однако мы всё же введём ещё один примитив: функцию блочного шифра XTEA. Выбор на неё пал из-за простоты реализации, чтобы не писать самостоятельно сеть Фейстеля из Salsa20. XTEA не самый быстрый, не самый безопасный (хотя имеет высокий порог), но это всё не так критично из-за того, что вызываться он будет ровно один раз при отправке пакета. Так как nonce не повторяется, то задумываться о режимах шифрования, векторах инициализации и подобном не нужно. Так как nonce кратен размеру шифроблока, то и дополнения не нужно.

В нашем случае ключом шифрования nonce будет являться выход Salsa20 с нулевым (который не используется обеими сторонами) nonce-ом. Вычисляется лишь один раз (после рукопожатия) и остаётся таким постоянно.

Остаётся последнее, о чём необходимо помнить: что делать с атакой перепроигрывания (replay attack). Суть её в том, что никто не запрещает перехватить пакет и отправить его повторно. Вариантов решения много: запоминать пакеты и смотреть, не встречаются ли дубляжи (это требует память), смотреть на временные штампы (это требует хорошую синхронизацию часов), либо, в нашем случае, просто запомнить последний полученный nonce противоположной стороны. Все пакеты с nonce ниже последнего будут игнорироваться. К сожалению, из-за природы UDP-пакеты в процессе передачи могут быть переупорядочены и поэтому будут происходить их потери при таком подходе. В нашей реализации (это уже протокола, как такового, не касается) мы разрешаем опционально иметь некое окно допустимых вариаций значений nonce-ов: компромисс между абсолютной защитой от повторных пакетов и относительно небольшим окном, когда можно осуществить подобную атаку на практике.

Получившийся транспортный протокол выглядит так:



Рукопожатие


Теперь рассмотрим протокол рукопожатия, в результате которого самое главное – это получить общий ключ шифрования и доверие к противоположной стороне (аутентифицировать её). В результате эта часть протокола будет работать всего-лишь один раз, суммарно передаст четыре пакета, но вся безопасность протокола полностью зависит от этого этапа рукопожатия.

Мы не используем и не хотим использовать PSK для шифрования: долгоживущий ключ это, конечно, просто, но в случае его утраты, компрометации мы сможем дешифровать весь перехваченный ранее трафик. Хочется иметь такое свойство, как совершенную прямую секретность. Для этого мы каждый раз, для каждой новой сессии, при каждом соединении генерируем новые ключи. Они временные, сессионные и находятся только в памяти программ и забываются при их выключении. Кроме того, существуют ограничения на максимальный размер данных, которые стоит шифровать одним ключом. Для Salsa20 эта граница довольно большая, но для спокойствия стоит о ней помнить и, например, производить обмен ключами (рукопожатие с нуля) каждые N гигабайт шифрованного трафика.

Чтобы обе стороны договорились об использовании одного и того же ключа, необходимо использовать протокол/алгоритм обмена ключей, такой как Диффи-Хельман. Суть его работы с точки зрения пользователя проста: обе стороны генерируют пару из публичного и приватного ключа, обмениваются по незащищённому каналу связи публичными частями, совершают вычисления и у них появляется общий секретный ключ. Перехватив публичные части ключей нетривиально узнать их приватные части или узнать получившийся общий.

В нашем протоколе будем использовать Curve25519: опять же, один из самых простых с точки зрения реализации в программе, обладающий отличной (возможно на данный момент и непревзойдённой) криптографической безопасностью, потрясающей производительностью. Алгоритм основан на работе с эллиптическими кривыми и поэтому имеет компактные размеры публичного ключа: 256 бит. Приватный ключ генерируется просто взяв 256 бит данных из PRNG операционной системы. Curve25519 эта функция, грубо говоря, позволяющая перемножать точки на кривой. Публичный ключ получается перемножением точки, являющейся приватным ключом, с вшитой в код константной. Общий секрет получается перемножением точки приватного ключа одной стороны с точкой публичного ключа противоположной.

Передав по одному пакету с публичным ключом от каждой стороны, мы в состоянии уже установить общий ключ, общий секрет. Однако мы не аутентифицируем стороны: мы не знаем, с кем общаемся. Очень часто в современных протоколах делают обмен ключами, потом их используют для шифрования канала связи, и уже по нему используют отдельный протокол аутентификации: например, передавая пароли или используя CHAP, или применяя асимметричную криптографию типа RSA. Во-первых, это означает большое количество round-trip-пакетов по сети, что не очень быстро. Во-вторых, если используется асимметричная криптография, то это ресурсоёмко и сложно.

В нашем протоколе мы используем общий ключ аутентификации сторон. Фактически пароль, но только высокоэнтропийный. Если задача стояла бы в обязательной аутентификации только клиента, то тогда поверх шифрованного сессионным ключом каналу можно было бы просто послать этот пароль и сравнить с тем, что в базе данных. Однако если будет сервер злоумышленника, то он узнает пароль. Можно использовать CHAP-протокол: он прост, быстр, но сервер злоумышленника узнает хэш от пароля/ключа. С одной стороны, не велика потеря, но это даёт возможность атаки уже на хэш-функцию.

Наилучшим выбором для нас является EKE: Encrypted Key Exchange – шифрованный обмен ключами. Это подвид семейства протоколов PAKE: password authenticated key exchange – аутентифицируемый паролями обмен ключами. Суть протоколов заключается в том, что именно в момент обмена ключами происходит аутентификация одной или двух сторон. В простейшем случае каждый из двух пакетов DH симметрично шифруется, в качестве ключа используется общий известный друг другу пароль. Если пароль не совпадает, то стороны получают некорректно дешифрованные публичные ключи, которые не дадут при перемножении совпадающий общий ключ.

При этом имеется такое свойство, как нулевое неразглашение (zero-knowledge proof), при котором ни бита информации о PSK-ключе не будет известно злоумышленнику: он получает зашифрованные данные (шум) и единственное, что он может предпринять, так это попытаться дешифровать, но у него нет возможности узнать правильно ли он нашёл публичный ключ (дешифровал ли его), так как он тоже является шумом. Именно это свойство решающее в нашем выборе DH-EKE протокола и им не обладают популярные SSH, TLS и прочие – у всех у них возможны атаки либо на асимметричную криптографию, либо на хэш-функции, либо на симметричные шифры.

Для шифрования используется всё тот же Salsa20. Так как на входе у него присутствует nonce, который никогда не должен повторяться, то каждый раз мы генерируем для него случайный nonce R и посылаем вместе с шифрованным публичным ключом DH со стороны клиента.

R, enc(PSK, R, ClientDHPubKey) --> Server

Сервер, зная общий ключ аутентификации, способен корректно расшифровать сообщение, сразу же вычислить общий секрет.

R, enc(PSK, R, ClientDHPubKey) --> Server
enc(PSK, R+1, ServerDHPubKey)  --> Client

Для аутентификации сторон мы передадим случайные числа (RS со стороны сервера) и ожидаем их получить снова в ответе после перешифрования:

R, enc(PSK, R, ClientDHPubKey)               --> Server
enc(PSK, R+1, ServerDHPubKey), enc(K, R, RS) --> Client
enc(K, R+1, RS)                              --> Server

Клиент после получения публичного ключа сервера способен вычислить общий ключ K, соответственно корректно дешифровать RS, зашифровать его и отправить на сервер. Сервер сравнивает RS с тем, что отправил (состояние должен сохранить в памяти) и тем самым убедиться, что клиент действительно знает PSK. Для аутентификации сервера делается аналогичное действие со случайным числом от клиента RC:

R, enc(PSK, R, ClientDHPubKey)               --> Server
enc(PSK, R+1, ServerDHPubKey), enc(K, R, RS) --> Client
enc(K, R+1, RS + RC)                         --> Server
enc(K, 0, RC)                                --> Client

В последнем случае в качестве nonce мы используем ноль, а можно R+2 или ещё что-нибудь подобное. Не принципиально: лишь бы не повторялось при использовании одного ключа, а ключ K уже уникальный для каждой сессии. Клиент, получив от сервера корректно дешифрованный RC убеждается, что он с сервером действительно имеют общий ключ и сервер знает PSK.

Общий ключ K может обладать не лучшей энтропией. Curve25519 всё же имеет не все 256 бит задействованными, соответственно, его криптографическая стойкость чуть меньше симметричного аналога 128 бит порога. Кроме того, недобропорядочная сторона может использовать специально созданные публичные ключи, которые при перемножении могут давать слабые ключи K: в теории такое не исключено, хотя на практике для Curve25519 не замечено. Но всё же параноидальные настроения можно будет удовлетворить, отдельно генерируя каждой стороной качественный высокоэнтропийный ключ и передавая его в пакетах, шифруемых K-ключом. Результирующий общий ключ, который уже и будет использоваться в транспортном протоколе, получается XOR-ом частей, созданных клиентом и сервером. Если какая-либо сторона будет недобропорядочна, то всё-равно достаточно одной высокоэнтропийной части ключа для хорошего ключа. На итоговой схеме ниже часть ключа клиента обозначена как SC, а сервера – SS.

И конечным штрихом будет отправка идентификатора клиента. Эта часть не связана с безопасностью протокола: она исключительно для удобства администрирования. Когда у нас несколько клиентов, то нужно как-то определять, какие ключи PSK необходимо использовать. Можно разграничивать клиенты и серверы по портам, но мы решили отправлять идентификатор явно. Идентификатор – это не секретная информация, но отправляя её в явном виде, мы не имеем анонимность и даём возможность применять DPI и чётко видеть, какой именно пользователь устанавливает соединение.

На данный момент идентификатор посылается в виде зашифрованного R (который уже присутствует в первоначальном пакете рукопожатия от пользователя), где в качестве ключа используется идентификатор клиента. В качестве шифра используется XTEA. Так как R каждый раз разный, то опять же, задумываться о векторах инициализации, режимах шифрования, не надо. Сервер не может сразу же детерминировано определять, каким ключом было произведено шифрование и он просто перебирает все известные ему идентификаторы клиентов и находит того, у кого результат дешифрования совпадает с R. Симметричный шифр – это быстрая операция и поэтому расходы тут несущественны, по факту на порядки меньшие, чем работа DH. В данном случае мы, конечно, предоставляем злоумышленнику и открытый текст и шифротекст, но криптоанализы XTEA показывают, что беспокоиться не о чем. В худшем случае злоумышленник узнает идентификатор клиента, но не PSK-ключ.

Итоговый вариант рукопожатия выглядит так:



Что ещё стоит сделать или исправить?


На данный момент пакеты рукопожатия имеют явную метку (оканчивается на нулевые байты): сервер сразу же может понять, что это не транспортный пакет. Если мы хотим противостоять DPI, то нужно убрать эту метку и сделать handshake-пакеты неотличимыми от шума. Исправлено в версии 2.3

Протокол на этапе рукопожатия и генерирования PSK-ключей зависит от качественного PRNG. Если PRNG предсказуем, слаб, то говорить о безопасности не приходится. Запускать программное обеспечение под закрытой проприетарной операционной системой и употреблять слово «безопасность» бессмысленно. Популярные Microsoft Windows и Apple OS X известно, что имеют непригодные для криптографии источники энтропии и PRNG. Можно было бы встроить собственный PRNG, например, на основе Fortuna, но это лишь отсрочка неизбежной утечки ключей, так как ОС всё-равно имеет полный доступ к памяти программ и никто не гарантирует, что информация из них не используется для лазеек.

Протокол обеспечивает конфиденциальность, аутентичность полезной нагрузки, двустороннюю zero-knowledge аутентификацию сторон, но данные о размерах и времени возникновения пакетов утекают в сеть. Решить это можно их фрагментацией и добавлением «шума»: посторонних, не связанных с настоящим трафиком пакетов. Исправлено в версии 3.0

Читайте также продолжение статьи Реализуем ещё более безопасный VPN-протокол!

Всего доброго, не переключайтесь!

© Сергей Матвеев, Python и Go разработчик ivi.ru

Наши предыдущие публикации:
» Лишние элементы или как мы балансируем между серверами
» Blowfish на страже ivi
» Неперсонализированные рекомендации: метод ассоциаций
» По городам и весям или как мы балансируем между узлами CDN
» I am Groot. Делаем свою аналитику на событиях
» Все на одного или как мы построили CDN

Комментарии (56)


  1. ValdikSS
    23.04.2015 16:47
    +2

    Отслеживаю проект с самого его появления, спасибо вам за него. Вообще, я считаю, идея делать VPN с одним типом шифрования — правильная. Во-первых, Salsa20 будет работать быстро как на мобильных устройствах, так и на десктопах; Google уже использует ChaCha20 в TLS для мобильных устройств, да и, например, в том же OpenVPN режимы, вроде OFB, уже давно особо никто не проверял и не использует.

    Честно говоря, чтобы проект взлетел, по моему мнению, нужны клиенты под все мейнстрим ОС (включая мобильные). Это, пожалуй, самое главное. Если бы у tinc были клиенты под мобильные ОС, да еще и с GUI, то он был бы явно популярнее OpenVPN (они, кстати, теперь тоже ChaCha20, Poly1305 и Ed25519-ключи используют).


    1. achekalin
      23.04.2015 17:08

      И обязательно клиенты под аппаратные роутеры. Mikrotik хотя бы, за ним D-Link. Cisco, как обычно, ничего такого не станет делать, но ОС-based VPN — это, увы, совсем не всегда удобно.


      1. IlyaEvseev
        24.04.2015 00:03

        это сарказьм?


        1. achekalin
          24.04.2015 15:35

          Вовсе нет. На железках какие VPN-ы стандартные (не фирменные) реализованы? IPSec, PPtP, L2TP, что еще? Если ни один из них не устраивает, то все грустно. Тот же Mikrotik имеет хотя OVPN и SSTP — оба не широко распространены (да и реализованы, кажется, по остаточному принципу — «когда есть время разработчиков»), но это хоть что-то.

          Посему, кроме реализации нового VPN под все популярные ОС, хочу уточнить, что на железе тоже нужна реализация, иначе так и будет новый протокол только для связи ПК.


    1. Disasm
      23.04.2015 22:14
      +1

      1. ValdikSS
        23.04.2015 22:17

        Ого! Спасибо!


      1. mva
        24.04.2015 13:06
        +1

        Кстати, сам сюда заходил чтобы сказать про Tinc :)
        // Кстати, в 1.1pre-версиях (апстримных, не android'ной морды) он стал куда более няшкой.
        // А вот с андроидной мордой есть косяк: ни оригинальный автор андроидной морды, ни я, не имеем понятия какие пермишны нужно прописать в манифест, чтобы получить доступ к /dev/tun. А без них требуется рут (ибо иначе приложение не может им пользоваться). Так и живём :(


  1. zhovner
    23.04.2015 16:52

    Насколько вероятно что Go будет поддерживаться эмбидед-системами: телевизорами, роутерами?
    Реально ли собрать GoVPN под arm, при том, что размер ПЗУ 10 мегабайт?


    1. ingrysty
      23.04.2015 17:13
      +1

      Оно уже сейчас свободно поддерживается, вопрос только в пока еще не существующих библиотеках. При условии, если мы говорим об ARM платформе.
      ПЗУ, конечно же, нужно будет иметь в запасе больше 50.


    1. stargrave2 Автор
      23.04.2015 17:14

      Под ARM 32bit он собирается. Сам ARM устройств не имею, но думаю что 10 мегабайт хватит, так как клиент и сервер бинарь скомпилированный и со сделанным strip занимает 2.7MiB.


  1. Ivan_83
    23.04.2015 17:57
    +3

    1. «ОС предлагают такую удобную штуку, как TUN/TAP: виртуальные сетевые интерфейсы.» — юзер спейс, медленно. Оно хорошо только для стадии разработки и дебага.
    Вся скорость чачи/сальсы на этом месте и потеряется.

    2. GO — в ядро не засунуть. Это путь в /dev/null, как и в случае со всякими джава и прочей хренью на которой пытаются писать впн и криптонеты.

    3. «В качестве алгоритма/функции шифрования выбираем Salsa20 (или вариант этого алгоритма ChaCha20).» — нет смысла, сам же DJB чачу рекомендует, как доработанный вариант сальсы.

    4. «Salsa20 на вход принимает кроме ключа с данными ещё и nonce: число, которое должно быть использовано только один раз для заданного ключа. Использование дважды фатально.» — Вы не разобрались как оно работает. Чтобы это было фатально нужно знать открытый текст, тогда можно про ХОРить и получить «маску ключа блока» (назову его так), далее этой маской маской можно ХОРить пакеты/блоки у которых тот же ключи и нонс и получать открытый текст.
    Но в начале нужно всё же знать открытый текст или иметь большую стат выборку и предположения по содержимому.
    Те это не фатальный косяк, тем более для случая когда нонс всего два раза повторился для ключа.


    1. ValdikSS
      23.04.2015 18:01

      юзер спейс, медленно. Оно хорошо только для стадии разработки и дебага.
      Вся скорость чачи/сальсы на этом месте и потеряется.
      Э? Вы серьезно? Гоняю гигабит через TUN по Ethernet point-to-point линку на компьютерах 7-8-летней давности, проблем никаких.


      1. Ivan_83
        23.04.2015 19:47
        +1

        Вполне.
        Это copyin + copyout + дополнительные параметры + проверки + переключения контекста при переходе от юзерспейса к ядру и обратно.
        Вот это всё заметно на фоне работы чачи/сальсы.
        А практической пользы в этом никакой нет, всё что описано выше — не сложно запилить прямо в ядре.

        Да, на х86 при одном-нескольких подключениях и больших пакетах оно не страшно, а вот когда подключений много и пакетрейт высокий тогда на операции связанные с переключением на юзерспейс и обратно станет уходить больше ресурсов чем на само шифрование.
        А мипс/арм мыльницы сдуются быстро и на малых нагрузках.


        1. stargrave2 Автор
          23.04.2015 21:13
          +4

          Не сложно запилить прямо в ядре? Вы учитываете и то что нужно вместо одной реализации писать две различные например чтобы поддерживать GNU/Linux и FreeBSD одновременно? Что проще поддерживать и администрировать: userpace демон требующий только TAP устройство или ядерные модули? О производительности надо париться когда она даёт о себе знать. Гигабиты гонять — проблема. Не у всех такие потребности. С таким же успехом можно было бы говорить и о том что на ассемблере гораздо производительнее софт без overhead-ов сторонних, но люди почему-то придумывают дополнительные абстракции, языки высокоуровневые. Стоимость разработки и поддержки ПО гораздо выше чем стоимость железа, как правило.


          1. Ivan_83
            23.04.2015 23:43

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

            Проще всего вообще ничего не шифровать, не передавать, пить пиво, гонять мяч и не парится.

            А ещё нужен форк под винду, мак и может что то ещё.
            Одним демоном никак не обойтись.

            Конкретно под фрю нужна нетграф нода и небольшой демон, по аналогии с mpd5.
            Под линукс тоже что то похожее.
            Под виндой придётся мучатся с RRAS и его виртуальными соединениями, ибо драйвер там рожать сложно и не целесообразно.
            Под маком хз.

            И далее, за ассемблер.
            Вы себе хотя бы представляете сколько применительно к сальсе/чаче может дать его использование?
            Разница в разы, а не какие то жалкие единицы процентов.
            Даже просто оптимизированный код на сях может дать 2-3 раза разницу по сравнению с референсом или не удачной реализацией. А версия на SSE/AVX сравнима/обгоняет AES-NI:
            events.yandex.ru/lib/talks/2693 (примерно с 03:00)

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

            И последнее.
            Студенческих поделок вида «демон для впн» — пруд пруди. Ко всем ним отношение одинаковое — этим пользуются иногда для себя и не более.
            Если код ядреный и его взяли в базу — это совершенно другой уровень.


            1. stargrave2 Автор
              24.04.2015 00:33
              +6

              Нужен форк под винду/мак? У меня контрвопрос: а зачем вам вообще это тогда всё надо, какие задачи вы хотите решить? Понятие криптографической безопасности на этих платформах исключено по определению в виду закрытости кода и поэтому что бы там не хотелось добиться, но либо GoVPN либо Windows/OS X — не тот инструмент которым можно решить задачу.

              Ваша боль на пустом месте по поводу производительности (вы ведь даже не спросили и не проверили а какую выдаёт сейчас то что сделано), ассемблере, контексте и желания засунуть всё в ядро, да ещё и упоминания Windows могут говорить только о том что вы смутно представляете трудозатраты на разработку ПО, его поддержку и обеспечения достойного качества. И последнее: студентов которые констатируют всем хорошо известные факты о дороговизне переключения контекста программ, об очевидности производительности ассемблера, о том что очевидно что на C это будет работать быстрее Go — пруд пруди, но отношение к ним одинаковое: они не знакомы с трудозатратами поддержки, тестирования, они закрывают глаза на то что десятилетиями вовсю внедрённые OpenSSL библиотеки полны багов, причём катастрофическим (не верят что это из-за сложности кода в который и смотреть то даже противно), не знают о большом разнообразии ОС и что писать ядерные вещи для них дело мягко говоря куда более дорогое чем всё железо за жизнь одного человека/компании вместе взятое.

              Когда от безопасности протоколов, их реализаций и софта будут зависеть ваши деньги (как минимум), то вы не будете рисковать и заставлять своих разработчиков писать ядерные модули, переписывать кучу всего на ассемблере. Вы пожелаете чтобы они на каждом чихе проверяли не выходит ли обращение к памяти за границы массивов, не переполнились ли где integer-ы и всё в таком духе. Вы будете рады знать что о тем что на каждую операцию делается в несколько раз больше проверок, чем могло бы быть на ассемблере, но если где-то проскочет какая-то дырка/лазейка которая может поставить весь ваш бизнес на грань краха, то вы лично пойдёте покупать ещё процессоров и серверов, потому-что их стоимость легко может быть пылинкой перед рисками сложного ПО: то, которое сложно оценивать, сложно review-ить. Или ПО в ядерном процессе, ещё в котором память может течь: гораздо дешевле иметь GC и userland в котором сильно всё ограничено по доступу.

              И когда к заказчику придёт новость о том найдена фатальная бага, как тьма находится в OpenSSL, то новость о том что «зато мы работали так быстро что сэкономили целый Xeon процессор» его не сильно обрадует.

              Безусловно количество перерастает в качество и если бы GoVPN работал со скоростью 1 килобайт/сек загружая на 100% процессор, то это одно, но GoVPN на одном ядре Xeon выдаёт почти 100Mbps.


            1. stargrave2 Автор
              24.04.2015 00:39
              +1

              И ещё замечу что в ядро с ходу я вспоминаю что встраивают IPsec. Он самый достойный и качественный? Отнюдь. OpenVPN вовсю работающий в userspace куда чаще используется. IPsec криптографически совершеннее? Он не плох, но например набор примитивов, мягко говоря, староват и все они очень и очень не быстрые (куда там до ChaCha20/Poly1305!).

              В базу например берут средства шифрования WiFi, как например WEP. Стандарт, супер распространённый. Очень он безопасен? TLS? Лучше не связываться. SSHv2 не шибко где какой стандарт и база, но закрыв глаза на пару недочётов он куда грамотнее и с Ed25519/ChaCha20/Poly1305 быстрее многих.

              Кто платит: тот быстрее и попадёт в «базу». А всё потому-что дорого. Не будут в OpenVPN писать ядерные модули.


              1. Ivan_83
                24.04.2015 02:00

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

                OpenVPN находится в нише:
                — не доверяю IPSec/L2TP/PPTP/SSTP/…
                — нужны не стандартные порты и стандартные TCP/UDP
                — не смог настроить IPSec/L2TP/PPTP/SSTP/…

                IPSec же вроде умеет AES, а на процах с AES-NI или системах где отдельный криптоускоритель они порвут чачу в том виде в котором она в GO.

                У вас, в отличии от OpenVPN, всё просто, я не вижу сложностей с запихиванием в ядро.
                Чачу/сальсу, поли, хтеа — я бы оставил в ядре, а рукопожатие и назначение адреса, создание интерфейса юзер спейс демону/утилите отдал.

                Давайте без паники, я писал ядерные модули для фри, и никто не умер :)
                habrahabr.ru/post/247743
                Ничего сложного там нет, разве что отладка бывает более муторной, по началу.
                В случае нетграфа можно делать как L2 так и L3 интерфейсы.

                «GoVPN на одном ядре Xeon выдаёт почти 100Mbps» — это катастрофа :)
                Мог бы выдавать больше гига (как минимум) если бы был в ядре.
                Да чёрт с ним, даже просто на сях оно было бы сильно шустрее, просто была бы просадка на мелких пакетах и высоких пакетрейтах, но сальса на SSE/AVX могла бы немного это компенсировать. (в ядре SSE/AVX трудно использовать)
                А желающие сами бы утащили в ядро.
                Ну или в NetMap, но это как то более специфично — всё таки сетевой интерфейс под него целиком уходит.


                1. stargrave2 Автор
                  24.04.2015 08:19
                  +2

                  У вас есть причины доверять Windows/OS X? А у меня нет, совсем. Вы можете гарантировать что не происходит ничего плохого в закрытом коде под которым это всё будет запускаться, что он не будет производить слив данных? Нет, так как нет открытого кода. Плюс там нет PRNG и источников энтропии которым бы можно было доверять и не реализовать свои, так как никто не гарантирует что энтропия не сольётся наружу. Много пользователей? Это их проблемы. Задача чего-либо связанного с безопасностью не выполнима под данной ОС.

                  OpenVPN находится в нише: более доверяемая реализация хотя бы косвенно из-за относительной простоты; гораздо более удобный для большинства (не для всех, нет) пользователей в плане администрирования. Именно простота играет решающую роль. Один бинарник и пара опций к нему и вот уже приемлимый готовый VPN.

                  AES он умеет. Лично я не доверяю AES, так как в нём есть уязвимости. Более того IPsec/OpenVPN и прочие не имеют zero-knowledge протоколов.

                  Я не говорю что написать ядерные модули это невозможно. Всё возможно. Вопрос трудозатрат и наличия ресурсов. Даже в случае с популярным OpenVPN это требует больших ресурсов (в виде человекочасов), а profit-а от этого будет так мало, что овчинка выделки не стоит. Ядерные модули это увеличение сложности, а значит потенциально больше возможностей для ошибок, недочётов, как это происходит с OpenSSL. Если ценой производительности можно добиться большей безопасности, то выбор падает именно на это.

                  Мог бы выдавать. Сейчас беготня за подобной производительностью это последнее о чём надо надо думать. И если уж вдаваться в конкретику, то реализация Salsa20 в используемой Go библиотеки написана как-раз таки на ассемблере, если что, на SSE. Вы пытаетесь оптимизировать шифрование/аутентификацию, хотя я уже говорил что они и так настолько быстрые что задумываться о Salsa vs ChaCha бессмысленно. Проседание идёт куда сильнее хотя бы на GC. Вариант решения: переписать с Go. Недостаток я думаю очевиден.


                  1. cebka
                    24.04.2015 14:29

                    Ну, насчет уязвимостей в AES вы загнули, конечно, конкретно. Нет, во влажных мечтаниях Бернштайна, конечно, любой алгоритм с s-блоками подвержен timing attack (если не используются constant time hardware implementation). Но на практике это не было и никогда не будет применимо, особенно по сети. А вот у salsa/chacha выход keystream однозначно более biased, чем выход от AES-CTR, что обнаруживается статистически. Я ненастоящий сварщик, чтобы из этого делать выводы, но и заявлять, что, дескать, chacha безопаснее AES, я бы сильно поостерегся.


                    1. stargrave2 Автор
                      24.04.2015 14:36

                      Да, я намекал только на timing attack-и на S-box-ы. Согласен что вопрос применимости на практике: очень хороший. Говорить что AES не безопасен это конечно не правильно или что Salsa/ChaCha сильно лучше в этом плане. Лично я ему просто поменьше доверяю, исключительно субъективно.


                      1. cebka
                        24.04.2015 14:56
                        +1

                        Ну так классические s-блоки были предназначены для реализации в железе, где они просто представляются дешифраторами (и однозначно имеют constant time для любых входных комбинаций). А в софте часто используют «оптимизации» AES на достаточно больших таблицах подстановки. Но даже в таком случае я не вижу абсолютно никакой реальной возможности timing атаки. Если мы берем SMP систему, где есть планировщик задач, прерывания и уж тем более, если мы берем сеть с ее непредсказуемым шумом. «Железный» aes по определеню constant time (те же AES-NI).

                        Насчет чачи у меня очень противоречивое мнение. С одной стороны, мне нравится реализация алгоритма в софте. С другой же, когда я внимательно смотрел пермутации чачи, мне это показалось очень-очень похожим на классическую s-p сеть, где s блок — это сложение по модулю 32, а p блок — ротации. Но однозначно я могу сказать только то, что глубина криптоанализа aes пока не сравнится с анализом чачи (хотя eSTREAM — это очень уважаемое мероприятие, бесспорно). И наблюдаемые на практике статистические bias'ы мне крайне не понравились:

                        paste.lisp.org/display/144927

                        AES-CTR прошел все тесты без проблем. RC4 я не тестировал, скажу сразу :)


                  1. Ivan_83
                    24.04.2015 16:52

                    Причин доверять нет, как и не доверять.

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

                    100 мегабит с ядра современного проца и не возможность распределить по ядрам — это приговор.

                    Вы лично проверяли что в Go оптимальная реализация сальсы на асме? :)
                    Никого преимущества GO перед Си не интересуют, потому что и то и то работает, только Go тормозит и код из него не утащить так просто.


                    1. stargrave2 Автор
                      24.04.2015 17:05

                      Не доверять причины есть: авторство и под кем в итоге создавался AES. AES != Rijndael.

                      Нет увеличения сложности? То есть вместо того чтобы написать один кусок кода который работает и под FreeBSD и под GNU/Linux, мне надо будет писать уже как минимум два. Действительно, никакой сложности.

                      Про 100Mbps уже говорил: если вас эта скорость не устраивает — не используйте. Приговор это например двойное использование nonce, так как тогда бы не выполнялись поставленные задачи перед проектом. С чего вы взяли что в этой реализации нет возможности распределения по ядрам?

                      Лично я видел что на ассемблере она там имеется. Для нужд большинства людей и на чистом Go скорость будет достаточная за глаза.

                      Хм. Ассемблер тоже работает и не тормозит. Ужас, куда же движется человечество изобретая бесполезные с вашей точки зрения (ну похоже на то) инструменты разработки, высокоуровневые языки и прочее, которое почти всегда делает куда более громоздкий и медленный код.


    1. cebka
      23.04.2015 18:17
      +3

      4. Иван, вы проявляете удивительное незнание базовых принципов криптографии. Повторение нонсов — это ужаснейшая проблема, которая делает автоматически любую шифросхему потенциально небезопасной. Почему? Да потому что это дает одинаковый ключевой поток k и k'. Тогда если k == k', то (P ^ k) ^ (P' ^ k') дает на выходе P ^ P', что явно отличимо от random output. Это даже *без* знания P и P' позволяет статистически выделить закономерности в таком шифротексте. Более того, в случае счетчика повторение нонса однажды значит повторение его и впоследствие, что позволит однозначно выделить закономерности и расшифровать содержимое. Повторение нонсов безопасно только в случае, если вы и так передаете /dev/random через зашифрованный канал. Что, очевидно, крайне редко применимый кейс. Зная открытый текст, конечно, все еще проще, но это знание необязательно в огромном количестве реальных случаев.


    1. stargrave2 Автор
      23.04.2015 18:27
      +2

      1. Медленно. Не спорю. Вопрос насколько. В GoVPN я 100Mbps почти выжимаю на одном ядре процессора. На самом деле скорость куда сильнее теряется на одном только garbage collector Go. Задача была сделать reviewable, поддерживаемый простой код, портируемый. Завязываться на ядро: так это писать для каждой ОС совершенно независимую реализацию.
      2. Если вам так критична скорость, то да. Производительность GoVPN удовлетворительна будет для большинства пользователей.
      3. Salsa20 перевесила только одним: она есть по-умолчаню из коробки в golang.org/x/crypto. Но тут это не шибко принципиально. Про ChaCha20 я поэтому и упомянул. Заменить на ChaCha20 это поменять название библиотеки. Но порог безопасности Salsa20 и так настолько высок, а разница в производительности на фоне того же garbage collector так незначительна, что тут уж проще выбрать что есть «из коробки», так как портируемость и простота поддержки лучше
      4. Вы не разобрались как это работает. Перехватив два шифротекста (сделанных одним ключём, одной PRNG последовательностью) и сделав XOR между ними мы получим XOR между двумя их открытыми текстами. Ни я, ни другие не говорят что вы сразу же получите на экране готовый к чтению открытый текст, но уже банальными методами простейшего криптоанализа, типа статистического, XOR открытых текстов тривиально достаётся особенно когда там человеческое письмо передаётся. Первое чему учит любая книга по прикладной криптографии это про одноразовые шифроблокноты, как идеальную систему шифрования, второе — как-раз что будет если послать два шифротекста одного ключа, третье — никогда не использовать выходы потоковых ключей дважды.


      1. Ivan_83
        24.04.2015 00:53

        1. 100 мегабит для одного ядра х86 (после 2007 года) — это очень мало.
        Чача20 на одном ядре Е8500 при блоках 512 байт даёт 176 мегабайт/сек (на самом деле больше, у меня там ещё другие накладные расходы были, пожалуй более тяжёлые чем ядерный приём/передача).

        2. В роутер на арм/мипс его уже не поставят: ГО большой, а конструкция будет давать мегабит 5-10 в лучшем случае.

        3. У вас сломается совместимость старых и новых клиентов, если просто поменять…

        4. Меня смутило только «дважды» и «фатально» :)
        ХОР между двумя открытыми текстами это больше чем ничего, но назвать его фатальным пожалуй перебор.

        5. У вас UDP, вы сами реализуете окно приёма.
        Про запрос повторной передачи пакета я ничего не увидел, ровно как и о прочих необходимых вещах, которые уже есть в TCP.
        Всё встаёт колом от потерянного пакета или просто едем дальше и пофиг на дропы?

        6. Почему UDP а не TCP?

        7. Так же я не увидел протокол для передачи управляющей инфы, только рукопожатие и передача данных.

        PS: не считаю проект плохим или не нужным, просто с такой реализацией (го+юзерспейс) область применения слишком сужается.


        1. stargrave2 Автор
          24.04.2015 01:35
          +1

          1. Это ваша субъективная оценка что мало. Лично меня и некоторых других пользователей *полностью* удовлетворяет и не напрягает нисколько. Я вот например в GnuPG и в полнодисковом шифровании выставляю Twofish вместо AES, хотя есть AES-NI: да, оно в разы медленнее получается, но зато мне спокойнее в плане безопасности. Смотря какие задачи вы хотите решать. Сделать линк между 10Gbps дата-центрами конечно не пойдёт.
          2. А в Raspberry Pi ставят. Go большой, а места там ещё больше. 5 Mbps — вполне удовлетворительно. Смотря какие задачи и требования.
          3. Ломается. Обновятся. Если заботится о совместимости дотошно, то так можно дожить и до 2015-го года и иметь всё ещё включённый SSLv3. Salsa20 имеет превосходную безопасность и она is good enough. ChaCha20 не факт что быстрее (зависит от процессора) и даже если и быстрее, то его вклад в процессор на уровне погрешности. Особо переживать по поводу ChaCha vs Salsa не вижу.
          4. Почитайте литературу. Для криптографов это фатально и абсолютно непригодно для использования.
          5. «Пофиг на дропы». Опять же, с чего вы взяли что это необходимо? Это VPN. Сеть. Сеть может дропать пакеты. Чтобы бороться с этим: используйте TCP, только на уровне выше. Ethernet же не перезапрашивает пакеты.
          6. Потому-что если в VPN гонять TCP, то выйдет TCP over TCP. Производительность тут при малейших потерях пакетов будет страдать так, что хоть 3DES будет вместо Salsa20 — ничего не поможет.
          7. Этой информации нет. И не планируется. Скорее даже принципиально, дабы не усложнять протокол, превращая его в TLS со всеми вытекающими. С ходу я даже не назову а какую информацию надо передавать. Разве что только heartbeat. О нём действительно в статье нет, но он конечно же есть чтобы давать о себе знать что противоположная сторона жива. Реализован он очень просто: так как у нас TAP, то передаются Ethernet frame-ы, так вот в MAC адресе фрейма прописывается константа HEARTBEAT. В будущем наверное появятся «шумовые» пакеты.


          1. Ivan_83
            24.04.2015 03:07

            1,2,3 — вы закапываете собственное творение.

            4. Не спорю, буквоедство с моей стороны.

            5,6. В некоторых применениях важно без потерь. Я поинтересовался, момент как то не был освещён.

            7. Я вот попытался представить себе как разделять код на ядерный и юзерспейсный и споткнулся на том что разделения нет.
            По сути ядерная часть должна пакеты гонять, а юзерспейсная рукопожатиями заниматься, конфигурить интерфейсы и вовремя прибивать старые.


            1. stargrave2 Автор
              24.04.2015 08:24
              +1

              1, 2, 3: вы так и не поняли задачи для которых оно создавалось. Задачи которое творение решает несовместимы с тем что предоставляют закрытые проприетарные ОС. Если вы рассчитывали на них решать задачи связанные с криптографической безопасностью, то это никак не моя оплошность.
              5,6: каких например? Я, честно говоря, впервые слышу про то чтобы предъявлялись требования к тому чтобы сеть гарантировала доставку пакетов. Для этого есть транспортные уровни на которых живут TCP.
              7. Вопрос совместимости/портируемости: Go работает не под «конкретно перечисленные ядра для которых написали модули», а под ОС предоставляющими «TAP интерфейс и где есть Go». В гораздо меньшем количестве кода я сделал более широкую поддержку платформ. Плюс вопрос безопасности: безусловно можно написать качественный код который бы безопасно можно запускать в ядре, не опасаться за безопасность, но вопрос трудозатра.


              1. Ivan_83
                25.04.2015 01:17

                1,2,3 — ничего не было про закрытые ОС.
                Ещё один тормозной VPN под линукс найдёт своих трёх пользователей, на этом всё и заглохнет.
                А ведь можно было подвинуть OVPN, просто написав на сях.

                5,6 — Иногда есть странный софт который не любит потерь пакетов. Тот же OVPN с TCP гарантирует доставку.

                7. За линукс не скажу, а вот нетграф работает под разными версиями и если не используется ничего за пределами нетграфа то изменения модуля практически требуются очень и очень редко.
                Можно посмотреть в svn — там многие модули нетграфа не меняли годами, либо правки носили косметический характер.

                Никого не интересует что go делает какие то там проверки, которые никому не нужны, гораздо хуже что всё тормозит и никуда это не встраивается.
                Страшилки про уязвимости рассказывайте в другом месте, у вас там всё до неприличия просто, настолько что даже без динамических буферов можно обойтись (только на этапе инита выделить), а код можно легко глазами пробежать.


                1. stargrave2 Автор
                  25.04.2015 09:22
                  -1

                  Не было про закрытые ОС? Вообще это очевидно. Если нет, то задачи GoVPN действительно будут не понятны и странны.

                  5,6 — пока вы не приводите конкретики и по мне это абсолютная выдумка. Если есть настолько странный софт который предполагает с какой-то стати что сетевой уровень ему должен гарантировать доставку, то как бы это проблемы такого кривого софта.

                  Ну что ж, используйте супер быстрый OpenSSL раз вы такой бесстрашный и C до сих пор является юзабельным языком. Человечество доказало что сложные вещи не способно писать на C так чтобы они были без уязвимостей. В OpenSSL фатальных уязвимостей нашли уже гору. Закрывайте на это глаза и радуйтесь производительностью.


    1. k0ldbl00d
      23.04.2015 20:46
      +1

      > GO — в ядро не засунуть
      А вы обратитесь к Леннарту Поттерингу, он еще и не такое засовывать пытался.


  1. cebka
    23.04.2015 18:36
    +1

    Немного не понял вашего описания ECDH.

    1.

    Curve25519 всё же имеет не все 256 бит задействованными, соответственно, его криптографическая стойкость чуть меньше симметричного аналога 128 бит порога.


    Вообще, автор curve25519 описывает пороговый уровень безопасности ECDH c этой кривой в 128 бит. Но значение rho для нее, все-таки, 126 бит: safecurves.cr.yp.to/rho.html

    2.
    Кроме того, не добропорядочная сторона может использовать специально созданные публичные ключи, которые при перемножении могут давать слабые ключи K: в теории такое не исключено, хотя на практике для Curve25519 не замечено.


    Традиционный способ ECDH на curve25519 — это генерировать сессионный ключ после одного раунда hsalsa (или hcacha). Например, как это делал я: github.com/vstakhov/rspamd/blob/master/src/libcryptobox/cryptobox.c#L146 (в nacl там hsalsa).

    3.
    При этом имеется такое свойство, как нулевое неразглашение (zero-knowledge proof), при котором ни бита информации о PSK-ключе не будет известно злоумышленнику: он получает зашифрованные данные (шум) и единственное, что он может предпринять, так это попытаться дешифровать, но у него нет возможности узнать правильно ли он нашёл публичный ключ (дешифровал ли его), так как он тоже является шумом.


    Немного не понял, почему вы считаете, что публичный ключ — это шум? На самом деле, публичный ключ — это точка на кривой, и если его не прятать специально (например, elligator'ом), то отличить его от шума элементарно. Ну и определение zero-knowledge proof все-таки неверно в таком случае.


    1. stargrave2 Автор
      23.04.2015 18:49

      1. Я брал классическое предположение что биты эллиптических кривых надо разделить на два. 256/2=128. 25519 это 256 бит, но несколько бит которого в приватном ключе явно устанавливаются в чётко заданные значения: раздел «Computing secret keys» на cr.yp.to/ecdh.html
      2. Да, действительно выхлоп DH прогоняется через HSalsa: github.com/stargrave/govpn/blob/develop/handshake.go#L109
      Я решил опустить эту мелочь из текста, хотя в коде делаю конечно же. Генерирование всё же результирующего секретного ключа из двух половинок передаваемых по каналу это сильно рекомендуемый совет уже из описания EKE протокола. Подчеркну: даже в EKE они говорят что не знают насколько это надо и полезно, лишь паранойя на всякий пожарный
      3. Шум с точки зрения внешнего наблюдателя. Для нас это конечно же перемноженный приватный ключ (который является выхлопом PRNG (плюс заменённых нескольких бит)) относительно константной точки


      1. cebka
        23.04.2015 19:06

        1. Надо делить не биты, а сложность дискретного логарифма, и даже не делить, а брать квадратный корень. Поэтому эти детерминированные биты в приватном ключе по факту не влияют на сложность дискретного логарифма (если вы не собрались, конечно, брутфорсить все 256 бит ключа, тогда да — их надо исключать из сложности такого перебора, но она будет заведомо намного больше, чем 128 бит).

        2. Это излишне, если вы применяете hsalsa. Задача атакующего тут в том, чтобы из ключа сервера и собственной ключевой пары подобрать такую пару, чтобы ее умножение давало бы гарантированно слабый ключ для hsalsa. Но это не даст ничего, т.к. выхлоп hsalsa даже со слабым ключом, очевидно, не будет давать слабого ключа для последующих применений salsa (хотя у rc4 есть определенные забавные ключи, да).

        3. Передаваемые вами публичные DH ключи — это не шум для внешнего наблюдателя. Если атакующий брутфорсит передаваемые данные, то ему достаточно проверять, лежит ли то, что он получил, на кривой curve25519 (а это делается просто подстановкой данных в уравнение), чтобы понять, правильно ли он расшифровал данные. После чего он получает PSK. Варианты коллизий точек, в принципе, возможны, но все равно мне не кажется, что это zero-proof.


        1. stargrave2 Автор
          23.04.2015 19:23

          1, 2. Возможно вы и правы. Но ведь хуже же не будет. Это тот же самый «на всякий пожарный» что и игнорирование 256 бит после первых 256 бит из Salsa20 которое делают в SSH и TLS реализациях. Насколько понимаю никто не доказал что они реально могут повлиять на безопасность алгоритма, но на всякий пожарный откидывают, чтобы внутреннее состояние полностью сротировалось. Я не криптограф и не решился отбрасывать рекомендацию DH-EKE по такому генерированию ключей.
          3. А мне вот кажется что коллизии там будут и гарантированно и достаточно много. Приватный ключ 25519 можно использовать и без установки трёх бит: то есть чистые 256 выхлопа PRNG, но ведь это тоже будет точкой на кривой. Так что любой рандом можно будет интерпретировать как точку на кривой.


          1. cebka
            23.04.2015 19:32

            1,2. Игнорируют из-за того, что размер блока сальсы и чачи 512 байт, а шифровать дробными блоками очень трудно и неудобно (т.е. есть негативный эффект на производительность). Да и reference имплементация и не умеет шифровать дробными блоками на самом деле.

            3. curve25519 задается уравнением y^2 = x^3 + 486662*x^2 + x. Публичный ключ — это точка на этой кривой в поле 2^255 ? 19. Точек, принадлежащих кривой, на этом поле значительно меньше, чем значений в поле. Но поскольку брутфорс салсы требует примерно 2^256 попыток, то да, коллизий будет очень много. Но если предположить, что есть более эффективный способ взлома PRF, то все хуже.


            1. stargrave2 Автор
              23.04.2015 21:39

              Всё же не очень понимаю где потеря производительности. То что выход Salsa/ChaCha имеет размер в 512 бит — действительно. Но это же не блочный шифр в котором нужно где-то выравнивать или дополнять. Все эти биты нужно XOR-ить с данными и раз уж мы получили какой-то выход, то можно было бы использовать сразу же. Если надо зашифровать 100 бит данных, то одна итерация Salsa/ChaCha даёт 512 бит из которых 256 бы пошли на Poly1305, а остальные 100 из оставшихся 256 на XOR с открытым текстом.

              Да, с публичными ключами вы всё же правы. То что мы на вход для секретного подаём 256 бит энтропии не должно означать что эти же 256 бит останутся и в публичном ключе. 25519 как-раз гарантии даёт относительно 128-бит уровня brute force как минимум.


              1. cebka
                23.04.2015 23:03

                Дело не столько в производительности, сколько в алгоритмическом усложнении. Допустим, мы зашифровали 256 бит подключа, у нас осталось еще 256 бит ключевого потока. Нужно это как-то сохранить в блоке, а потом начать шифровать вначале куском того keystream, что у нас имеется, а потом уже начинать новый раунд пермутаций. Это существенное усложнение кода, и рефренсная имплементация этого не делает. Более того, nacl Бернштайна и libsodium при определении cryptobox и secretbox из-за этого делают вот эти вот уродливые cryptobox_ZEROBYTES = 32, а cryptobox_BOXZEROBYTES = 16, что делает сущим мучением применение этих примитив на практике. То есть, делается так:

                Берется весь поток, в котором первые 32 байта (т.е. полный блок chacha/salsa) забиваются нулями. Дальше прилепляется plaintext. Дальше делается xchacha/xsalsa с заданным ключом на все данные (т.е. 32 байта нулей и весь payload). Потом первые 16 байт используются как ключ для poly1305, а в оставшиеся 16 байт записывается mac от шифротекста без первых 32-х байт. Расшифровка еще хуже: берется 32 байта, зануляются, потом по ним делают полную итерацию xchacha/xsalsa и получают искомые 16 (!) байт ключа для poly1305, потом проверяется целостность шифротекста, а дальше прогоняется полная итерация xchacha/xsalsa по всему payload, а первые 32 уже байта забиваются нулями.

                Вот такой вот подход позволил избежать кусковых блоков, но с другой стороны добавил лишние нули в конструкции и постоянно требует копирования шифротекста/плейнтекста, что очень неудобно на практике (и в реализации протоколов).


                1. stargrave2 Автор
                  23.04.2015 23:13

                  В GoVPN реализации именно так и делается тоже: создаётся участок забитый нулями, на него Salsa20 выхлоп натравливается, где из начала берутся данные для Poly1305, а нужная часть XOR-ится с plaintext-ом. Ну и дешифрование аналогично. Появляются нули, но так действительно проще. В целом начинаю понимать что действительно проще будет делать если итерироваться поблочно по выходам Salsa/ChaCha. Конкретно в Go библиотеке работа с блоками скрыта и там задаётся конкретная длина сколько надо сгенерировать байт.

                  PS: всё же не 32 байта, а 64 (полный блок) и на вход Poly1305 32 байт, вместо 16 подаётся в качестве ключа.


                  1. cebka
                    23.04.2015 23:23

                    Ну вот как раз нулей я хотел избежать по причине сложностей с копированием и лишних маркеров для анализаторов (все-таки 16 нулей последовательно нечасто встречаются в реальных данных): github.com/vstakhov/rspamd/blob/master/src/libcryptobox/cryptobox.c#L162

                    Да, конечно же вторая часть первого блока используется уже для payload. И да, мой подход хуже, если в протоколе много очень коротких сообщений (меньше 32-х байт), т.к. делается дополнительный раунд пермутаций. С другой же стороны, он более скрытный и простой в плане адаптации существующего кода.


  1. cebka
    23.04.2015 18:43

    Да, еще такой вопрос: откуда вы берете и как передаете PSK в вашей схеме? Ведь я правильно понимаю, что вы генерируете его из пароля (в таком случае вопрос к PRNG становится вообще неясным, для чего нужны вам криптографически стойкие случайные числа).


    1. stargrave2 Автор
      23.04.2015 18:52

      Нет, сейчас PSK это вне программы сгенерированный ключ. В простейшем случае просто dd if=/dev/random bs=32 count=1: github.com/stargrave/govpn/blob/develop/utils/newclient.sh#L6. Из пароля предполагается генерирование с внедрением Secure Remote Password протокола, пока это на уровне TODO: github.com/stargrave/govpn/blob/develop/TODO#L3

      PRNG нужен для создания сессионных ключей, которые действуют только на протяжении одной сессии. PSK используется только для аутентификации сторон на этапе рукопожатия.


      1. cebka
        23.04.2015 18:56

        Крайне странный подход. Ведь вам нужно этот psk как-то еще и передать между клиентом и сервером. А в случае пароля достаточно взять PBKDF и применить его клиентом и сервером. Или же вообще взять OTR и их 'Socialist millionaire problem' подход к проверке сторон (который, кстати, действительно является zero-proof протоколом).


        1. stargrave2 Автор
          23.04.2015 19:01

          DH-EKE точно так же является zero-knowledge протоколом (замечу что не я его изобрёл :-), он известен давно). PSK мне не надо передавать. Он используется в дебрях EKE нигде напрямую не передаваясь. В этом и суть DH-EKE. Пароль известный в полной мере и клиенту и серверу — не вариант. PAKE протоколы давно изобретены с приставкой augmented: когда серверй действительно не знает пароля клиента. SRP относится к таким.

          В augmented PAKE протоколах пароль в чистом виде известен только клиенту. Сейчас EKE это не augmented схема: PSK известен в равной степени обоим. Поэтому и смотрим в сторону SRP.


          1. cebka
            23.04.2015 19:09

            Да, я сейчас прочитал про EKE, и понял, что был неправ. Просто из вашей статьи не следует полного описания самого EKE и получения на его основе PSK, отчего я решил, что PSK заранее подкладывается клиенту и серверу.


  1. reji
    23.04.2015 19:21

    К сожалению, неюзабельно на embeded платформах из-за GO — развер в даже в 2МБ очень велик.


  1. Disasm
    23.04.2015 22:22

    > Но уже нормальной практикой стало генерирование ключа аутентификации для каждого сообщения на основе выхода Salsa20 PRNG этого пакета.
    Можно узнать, где и с каких пор это стало нормальной практикой?


    1. stargrave2 Автор
      23.04.2015 22:27

      1. Disasm
        23.04.2015 22:49

        Жесть какая:

        The reason for generating the Poly1305 key like this rather than
        using key material from the handshake is that handshake key material
        is per-session, but for a polynomial MAC, a unique, secret key is
        needed per-record.

        Обоснование ни о чём. MAC и без этого будет достаточно уникальным, хотя бы потому, что гамма генерируется каждый раз разная, поэтому и MAC от неё будет разный, даже если его через HMAC считать. Плюсы предложенного подхода очень не очевидны. Зато шанс ухудшения криптостойкости ненулевой, так скажем.


        1. cebka
          23.04.2015 23:18

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


          1. Disasm
            23.04.2015 23:20

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


            1. stargrave2 Автор
              23.04.2015 23:27

              MAC считает просто только от зашифрованных данных? А где же секретная часть, ключ? Это какой-то просто хэш получается, а не MAC.


              1. Disasm
                23.04.2015 23:29

                Ключ для MAC берётся на этапе согласования ключей и не совпадает ни с какими другими ключами.


            1. cebka
              23.04.2015 23:28
              +1

              Все хорошо, но откуда вы возьмете ключ для MAC'а? Напомню, что poly1305 в данном виде не содержит финального раунда aes и не принимает, соответственно nonce. У HMAC, напомню также, такой проблемы нет — там можно использовать один и тот же ключ сколь угодно долго. Хотя на практике его все равно ротируют, чтобы ограничить время на поиск коллизий.