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

    Транспортные протоколы 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
    • 5,6k
    • 9
    Поделиться публикацией

    Комментарии 9

      +1

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

        +2
        Джава или i2pd? Для i2pd параметр precomputation.elgamal=true включает вот это, что резко снижает нагрузку на процессор.
        NTCP2 же эту тормозную операцию modpow не использует совсем.
          0
          Была java. Спасибо, почитаю про i2pd.
          0
          Мне нравится концепция i2p как «параллельной интернет». Но практика показывает, что оно немногим нужно. TOR гораздо популярнее как раз из-за того, что через него можно ходить в обычную сеть.
          Абсолютно согласен. Будем надеяться, что i2p станет со временем удобней и стабильней. Мне тоже нравится эта концепция. Make I2P great again! :)
            0
            Мне Tor видится как переходная технология. Просто он имеет значительное финансирование и больше разработчиков, включая ряд оплачиваемых. Он уже есть во многих прошивках роутеров и имеет свой собственный браузер. I2P просто нужно время.
            –1
            i2p станет еще медленнее?
              0
              С NTCP2 быстрее стал i2p, пока предрелизный вариант имеется. Ждем релиз ;)
                0
                в репозитории OpenWRT нет последней версии 2.22, роутеры ~500mhz уже не тащат i2pd?
                п.с. было бы неплохо если была бы чёткая инструкция для развёртывания на ххWRT. Или дефотный конфиг с указанными необходимыми правками.
                  0
                  Мы сами не занимаемся сборками под OpenWRT. Этим занимается LLE.
                  Вот тема github.com/PurpleI2P/i2pd/issues/345
                  Вроде с 2.22 там все в порядке.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое