Транспортные протоколы 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 в дополнение к двум уже существующим ключам (шифрования и подписи) вводится третий ключ x22519, называемый статическим ключом, который обязательно должен присутствовать в каком нибудь адресе 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=<порт> для входящих соединений.

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


  1. dmitryredkin
    13.08.2018 09:09

    Мне нравится концепция i2p как "параллельной интернет". Но практика показывает, что оно немногим нужно. TOR гораздо популярнее как раз из-за того, что через него можно ходить в обычную сеть.
    Ну и реализация конечно пока хромает. Год назад вынужденно снял узел i2p с роутера, ибо никаким ухищрениями не удавалось добиться, чтобы он жрал меньше процессора (стоит один из первых атомов).


    1. orignal Автор
      13.08.2018 14:01
      +1

      Джава или i2pd? Для i2pd параметр precomputation.elgamal=true включает вот это, что резко снижает нагрузку на процессор.
      NTCP2 же эту тормозную операцию modpow не использует совсем.


      1. dmitryredkin
        14.08.2018 06:48

        Была java. Спасибо, почитаю про i2pd.


    1. ciphertext
      13.08.2018 16:23

      Мне нравится концепция i2p как «параллельной интернет». Но практика показывает, что оно немногим нужно. TOR гораздо популярнее как раз из-за того, что через него можно ходить в обычную сеть.
      Абсолютно согласен. Будем надеяться, что i2p станет со временем удобней и стабильней. Мне тоже нравится эта концепция. Make I2P great again! :)


    1. Quei
      13.08.2018 17:41

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


  1. be52
    13.08.2018 13:56

    i2p станет еще медленнее?