Comments 7
Есть несколько мыслей по оптимизации вашей схемы. У вас получается что клиент сможет отправить первый байты полезных данных (например имя api endpoint-a) только после полного раундтрипа? А что насчет сервера? Если сервер вычислил ключ шифрования уже на первом upd-пакете от клиента то значит он имеет теоретическую возможность отправить зашифрованные данные тут же не дожидаясь запроса от клиента, правильно? То есть сервер генерирует "ephemeral server public key" записывает его в udp-пакет который предназначен клиенту и тут же внутри этого же udp пакета следом записывает уже зашифрованные данные и тогда клиент получив кей-шару от сервера вычисляет симметрический ключ шифрования и дальше сможет расшифровать уже полученные данные от сервера.
В общем это довольно интересная техника которая позволяет избежать лишнего сетевого раундтрипа в случае если серверу нужно безусловно отправить клиенту какие-то данные (например свою версию api-ручек чтобы клиент увидев что у него старая версия бинарника которая не совместима с ними попросил юзера обновить приложение).
И дальше появляются мысли как можно еще сильнее зарыться в оптимизацию сетевых раундтрипов - если имя api-ручки сервера не является таким уж и секретным то клиент может включить это имя (в незашифрованном виде) в тот свой первый udp-пакет и тогда сервер сможет выполнить полученный запрос и отправить уже зашифрованные данные клиенту уже в том первом udp-пакете который клиент получит от сервера.
И кстати тут же напрашивается идея что можно сэкономить драгоценное место в upd-пакете (1472 байт) и закодировать имя api-ручки в номере udp-порта - то есть сервер будет слушать не один порт а например тысячу сетевых портов по одному на каждую api-ручку, правда для этого нужно чтобы это зашифрованное соединение не было привязано не только к ip-аддресу и порту клиента (что в принципе и так является хорошей практикой чтобы например избежать установки нового соединения при смене клиентом wi-fi сети на мобильную сеть и наоборот) но и к порту сервера.
Ну и наконец появляется такая идея что этот "ephemeral server public key" можно вообще включить в бинарник приложения и тогда клиент сможет вычислить ключ для симметричного шифрования вообще без общения с сервером и тогда он сможет в своем первом upd-пакете (включив сначала свой "ephemeral public key" ) отправить серверу в зашифрованном виде как имя api-endpoint-а так и соответствующие данные.
Спасибо за размернутый фидбэк и советы.
Как сказано в начале статьи этот протокол в первую очередь пишется для месенджера, где будет создаваться долгоживущее соединение и все данные будут перегоняться через него.
Идея кодирования API ручки в номере порта довольно интересная и поможет сэкономить байты в UDP пакете, но поскольку в моем сценарии предполагается долгоживущее соединение, использование одного порта для всей сессии будет более выгодным и простым в менеджменте. Однако, этот подход можно добавить в виде опциональной возможности, которая будет выгодна сервисам, создающим единичные, короткие get запросы. Не знаю на сколько целесообразно смешивать два подхода в рамках одного протокола и это внесет сложность в реализацию, по этому на данный момент реализовывать эту возможность я не стану, но обязательно подумаю над этим позже.
Оптимизация раундтипов хорошая возможность для улучшения. Это очень важно для скорости запуска и бесшовных переподключений, но внедрение нулевого раундтипа приведет к сложностям в виде защиты от reply атак, что может быть критично. К тому же я описывал механизм востановления сессии, который не успел реализовать, который должен значительно сократить время повторного подключения. И действительно нужен ли нулевой раундтип в добавку к механизму возобновления сессии нужно будет проверять эксперементально, после его реализации. В первой версии протокола я оставлю текущую схему, но если эксперемент покажет критические зависания связанные с 1-RTT, придется его оптимизировать.
А запекание ephemeral ключа приведет к проблемам с PFS, а в данном случае для меня безопасность приоритетнее скорости
Нет формального обоснования - алгоритм в помойку
Несколько я знаю, в криптографии основное правило: если ты не эксперт, то пользуйся готовыми алгоритмами и протоколами
Я вот этот момент никогда понять не мог
Дело в том, что
S— это результат математической операции, и использовать его «как есть» в качестве ключа небезопасно.
S мы можем получить, только если как-то утекли приватные ключи или как-то взломали сам алгоритм. А если у нас уже есть S, то мы точно так же можем применить KDF и получить нужные сессионные ключи. Тогда какая разница?
В начале тоже не понимал в чем вообще смысл этого "Лишнего шага", пока писал свой протокол как раз и разабрался в этом.
Суть в том, что S - результат заранее известной математической операции, по этому оно может иметь предсказуему структуру, иметь какой-то паттерн. Например, каждый второй бит S обязательно 0 (пример взят из воздуха). Из-за чего криптоанализ упрощается и надежность падает. Деревация ключа делает его чуть более непредсказуемым.
А еще S может быть просто слишком длинным или слишком коротким для использования в качестве ключа шифрования, деревация помогает привести его к нужной длине используя весь ключ (а не просто отсечь лишние байты, или заполнить недостающие нулями)
Как я свой гибридный протокол шифрования за выходные написал