Как стать автором
Обновить

Реализация нового транспортного протокола NTCP2 сети I2P

Время на прочтение6 мин
Количество просмотров9.1K
Транспортные протоколы I2P были разработаны почти 15 лет назад, когда основной задачей было сокрытие содержимого трафика, а не факт использования того или иного протокола. DPI(deep packets inspection) и блокировку трафика в то время никто не принимал в расчет. Однако времена меняются и хотя существующие протоколы I2P по прежнему защищены довольно хорошо, возникла необходимость в новом транспортном протоколе, отвечающему на существующие и будущие угрозы, и, в первую очередь, DPI, анализирующий длину пакетов. Помимо этого, новый протокол использует самые современные достижения криптографии. Полное описание протокола здесь. За основу взят Noise, в котором в качестве хэш-функции используется SHA256, а в качестве DH (в терминологии Noise) — x25519.

image

Новая криптография


Для NTCP2 в дополнение к уже существующим в I2P необходимо реализовать следующие криптографические алгоритмы:

  • x25519
  • HMAC-SHA256
  • Chacha20
  • Poly1305
  • AEAD
  • Siphash

Все они, за исключением Siphash, реализованы в openssl 1.1.0. В свою очередь Siphash появится в openssl 1.1.1, релиз которого состоится в ближайшее время. Для совместимости с openssl 1.0.2, входящей в большинство ныне используемых ОС, в i2pd были добавлены собственные реализации, написанные одним из разработчиков i2pd Jeff Becker-ом, известным в I2P как psi.

По сравнению с NTCP x25519 заменяет DH, AEAD/Chaha20/Poly1305 заменяет AES-256-CBC/Adler32, а Siphash используется для шифрования длины передаваемых сообщений. Процедура вычисления общего ключа стала более сложной: с множеством вызовов HMAC-SHA256.

Изменения в RouterInfo


Для работы по протоколу NTCP2 в дополнение к двум уже существующим ключам (шифрования и подписи) вводится третий ключ x25519, называемый статическим ключом, который обязательно должен присутствовать в каком нибудь адресе RouterInfo как параметр «s» и для клиентов и для серверов. Если более одного адреса поддерживают NTCP2, например ipv4 и ipv6, то «s» обязан быть везде одинаковым. Для клиентов адрес может содержать только «s» и не содержать параметры «host» и «port». Также обязательным параметром NTCP2 является «v», в настоящий момент всегда равный «2».

Адрес NTCP2 может задаваться как адрес типа «NTCP» с дополнительными параметрами — в этом случае соединение может быть установлено как по NTCP так и по NTCP2, или же как адрес типа «NTCP2», поддерживающий только NTCP2 соединения. В джавовском I2P применяется первый способ, в i2pd — второй.

Если узел принимает входящие NTCP2 соединения, то он должен опубликовать параметр «i» со значением IV для шифрования публичного ключа при установке соединения.

Установка соединения


В процессе установки соединения стороны генерируют пары временных ключей x25519, и на основе их и статических ключей вычисляют наборы ключей для передачи данных. Также происходит проверка подлинности статических ключей и соответствие содержимому RouterInfo.

Стороны обмениваются тремя сообщениями:

SessionRequest ------------------->
< — SessionCreated
SessionConfirmed ----------------->

для каждого из которых вычисляется общий ключ x25519, называемый «input key material», и затем генерируется ключ шифрования сообщения с помощью операции MixKey, при этом значением ck (chaining key) сохраняется между сообщениями и является результатом, на основе которого вычисляются ключи для передачи данных. Реализация MixKey выглядит примерно так:

Код MixKey
 void NTCP2Establisher::MixKey (const uint8_t * inputKeyMaterial, uint8_t * derived)
{
	// temp_key = HMAC-SHA256(ck, input_key_material)
	uint8_t tempKey[32]; unsigned int len;
	HMAC(EVP_sha256(), m_CK, 32, inputKeyMaterial, 32, tempKey, &len); 	
	// ck = HMAC-SHA256(temp_key, byte(0x01)) 
	static uint8_t one[1] =  { 1 };
	HMAC(EVP_sha256(), tempKey, 32, one, 1, m_CK, &len); 	
	// derived = HMAC-SHA256(temp_key, ck || byte(0x02))
	m_CK[32] = 2;
	HMAC(EVP_sha256(), tempKey, 32, m_CK, 33, derived, &len); 	
}


SessionRequest состоит из 32-х байтного публичного ключа x25519 клиента, и зашифрованного AEAD/Chacha20/Poly1305 16 байтного блока данных + 16 байт хэша, а также набор случайных данных (padding), длина которого передается в зашифрованном блоке. Также там передается длина второй половины сообщения SessionConfirmed. Блок шифруется и подписывается ключом на основе временного ключа клиента и статического ключа сервера. Начальный ck для MixKey устанавливается в SHA256 («Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256»).

Поскольку 32 байта публичного ключа x25519 могут быть распознаны dpi, то они шифруются с помощью AES-256-CBC, где ключом служит хэш адреса сервера, а IV берется из параметра «i» адреса в RouterInfo.

SessionCreated по структуре аналогично SessionRequest, за исключением того, что ключ вычисляется на основе временных ключей обеих сторон, а в качестве IV для шифрования/расшифровки публичного ключа берется IV после расшифровки/шифрования публичного ключа из SessionRequest.

SessionConfirmed состоит из двух частей: статический публичный ключ клиента и RouterInfo клиента. В отличие от предыдущих сообщений, публичный ключ шифруется AEAD/Chaha20/Poly1305 с тем же ключом, что и SessionCreated. Поэтому длина первой части не 32, а 48 байт. Вторая часть шифруется тоже AEAD/Chaha20/Poly1305, но с новым ключом, вычисляем на основе временного ключа сервера и статического ключа клиента. Также к RouterInfo может быть добавлен блок случайных данных, но, как правило, в этом нет необходимости, потому что длина RouterInfo разная.

Генерация ключей для передачи данных


Если все проверки хэшей и ключей в процессе установки соединения прошли успешно, то после последнего MixKey на обеих сторонах должен быть одинаковый ck, из которого будут сгенерированы 2 набора троек ключей <k, sipk, sipiv> в каждую сторону свой, где k — ключ AEAD/Chaha20/Poly1305, sipk — ключ для Siphash, sipiv — начальное значение IV для Siphash, которое изменяется после каждого его применения.

Код, реализуюший генерацию ключей
void NTCP2Session::KeyDerivationFunctionDataPhase ()
{
	uint8_t tempKey[32]; unsigned int len;
        // temp_key = HMAC-SHA256(ck, zerolen)
	HMAC(EVP_sha256(), m_Establisher->GetCK (), 32, nullptr, 0, tempKey, &len); 
	static uint8_t one[1] =  { 1 };
        // k_ab = HMAC-SHA256(temp_key, byte(0x01)).
	HMAC(EVP_sha256(), tempKey, 32, one, 1, m_Kab, &len); 
	m_Kab[32] = 2;
        // k_ba = HMAC-SHA256(temp_key, k_ab || byte(0x02))
	HMAC(EVP_sha256(), tempKey, 32, m_Kab, 33, m_Kba, &len);  
	static uint8_t ask[4] = { 'a', 's', 'k', 1 }, master[32];
        // ask_master = HMAC-SHA256(temp_key, "ask" || byte(0x01))
	HMAC(EVP_sha256(), tempKey, 32, ask, 4, master, &len); 
	uint8_t h[39];
	memcpy (h, m_Establisher->GetH (), 32);
	memcpy (h + 32, "siphash", 7);
        // temp_key = HMAC-SHA256(ask_master, h || "siphash")
	HMAC(EVP_sha256(), master, 32, h, 39, tempKey, &len); 
        // sip_master = HMAC-SHA256(temp_key, byte(0x01))  
	HMAC(EVP_sha256(), tempKey, 32, one, 1, master, &len); 
        // temp_key = HMAC-SHA256(sip_master, zerolen)
	HMAC(EVP_sha256(), master, 32, nullptr, 0, tempKey, &len); 
       // sipkeys_ab = HMAC-SHA256(temp_key, byte(0x01)).
	HMAC(EVP_sha256(), tempKey, 32, one, 1, m_Sipkeysab, &len); 
	m_Sipkeysab[32] = 2;
         // sipkeys_ba = HMAC-SHA256(temp_key, sipkeys_ab || byte(0x02)) 
	HMAC(EVP_sha256(), tempKey, 32, m_Sipkeysab, 33, m_Sipkeysba, &len);
}


Первые 16 байт массива sipkeys представляют собой собой ключ Siphash, вторые 8 байт — IV.
На самом деле для Siphash требуется два ключа по 8 байт, но в i2pd они рассматриваются как 1 ключ длиной 16 байт.

Передача данных


Данные передаются фреймами, каждый фрейм состоит из 3-х частей:

  1. 2 байта длины фрейма, зашифрованной Siphash
  2. данные, зашифрованные Chacha20
  3. 16 байт хэша Poly1305

Максимальная длина передаваемых данных в одном фрейме — 65519 байт.

Длина сообщения шифруется применением операции XOR с первыми двумя байтами текущего IV Siphash.

Данные состоят из блоков, каждому блоку предшествует 3-х байтный заголовок с типом блока и длиной. В основном передаются блоки типа I2NP, содержащие сообщения I2NP с измененным заголовком. В одном фрейме может быть передано несколько I2NP блоков.

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

Кроме них, в текущей реализации NTCP2 встречаются еще 3 типа блока:

  • RouterInfo — обычно содержит RouterInfo сервера сразу после установки соединения, но может быть передано и RouterInfo произвольного узла в любой момент с целью ускорения работы floodfill-ов, для чего в сообщении предусмотрено поле флагов.
  • Termination — отсылается узлом при разрыве соединения по его инициативе с указанием причины.
  • DateTime — текущее время в секундах.

Таким образом, новый транспортный протокол позволяет не только эффективно противостоять DPI, но и существенно снижает нагрузку на процессор за счет более современной и быстрой и криптографии, что особенно важно при работе на слабых устройствах типа смартфонов и маршрутизаторах. В настоящий момент поддержка NTCP2 полностью реализована как в официальном I2P, так и в i2pd и появится официально в следующих релизах 0.9.36 и 2.20 соответственно. Для включения ntcp2 в i2pd следует указать конфигурационный параметр ntcp2.enabled=true, и ntcp2.published=true и ntcp2.port=<порт> для входящих соединений.
Теги:
Хабы:
Всего голосов 32: ↑32 и ↓0+32
Комментарии9

Публикации

Истории

Ближайшие события

One day offer от ВСК
Дата16 – 17 мая
Время09:00 – 18:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург