Транспортные протоколы I2P были разработаны почти 15 лет назад, когда основной задачей было сокрытие содержимого трафика, а не факт использования того или иного протокола. DPI(deep packets inspection) и блокировку трафика в то время никто не принимал в расчет. Однако времена меняются и хотя существующие протоколы I2P по прежнему защищены довольно хорошо, возникла необходимость в новом транспортном протоколе, отвечающему на существующие и будущие угрозы, и, в первую очередь, dpi, анализирующий длину пакетов. Помимо этого, новый протокол использует самые современные достижения криптографии. Полное описание протокола здесь. За основу взят Noise, в котором в качестве хэш-функции используется SHA256, а в качестве DH (в терминологии Noise) — x25519.
Для NTCP2 в дополнение к уже существующим в I2P необходимо реализовать следующие криптографические алгоритмы:
Все они, за исключением 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.
Для работы по протоколу 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 выглядит примерно так:
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, которое изменяется после каждого его применения.
Первые 16 байт массива sipkeys представляют собой собой ключ Siphash, вторые 8 байт — IV.
На самом деле для Siphash требуется два ключа по 8 байт, но в i2pd они рассматриваются как 1 ключ длиной 16 байт.
Данные передаются фреймами, каждый фрейм состоит из 3-х частей:
Максимальная длина передаваемых данных в одном фрейме — 65519 байт.
Длина сообщения шифруется применением операции XOR с первым двумя байтами текущего IV Siphash.
Данные состоят из блоков, каждому блоку предшествует 3-х байтный заголовок с типом блока и длиной. В основном передаются блоки типа I2NP, содержащие сообщения I2NP с измененным заголовком. В одном фрейме может быть передано несколько I2NP блоков.
Другим важным типом блока является блок случайных данных, который рекомендуется добавлять к каждому фрейму. Он может быть только один и последним.
Кроме них, в текущей реализации NTCP2 встречаются еще 3 типа блока:
Таким образом, новый транспортный протокол позволяет не только эффективно противостоять dpi, но существенно снижает нагрузку на процессор за счет более современной и быстрой и криптографии, что особенно важно при работе на слабых устройствах типа смартфонов и маршрутизаторах. В настоящий момент поддержка NTCP2 полностью реализована как в официальном I2P, так и в i2pd и появится официально в следующих релизах 0.9.36 и 2.20 соответственно. Для включения ntcp2 в i2pd следует указать конфигурационный параметр ntcp2.enabled=true, и ntcp2.published=true и ntcp2.port=<порт> для входящих соединений.
Новая криптография
Для 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-х частей:
- 2 байта длины фрейма, зашифрованной Siphash
- данные, зашифрованные Chacha20
- 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=<порт> для входящих соединений.
dmitryredkin
Мне нравится концепция i2p как "параллельной интернет". Но практика показывает, что оно немногим нужно. TOR гораздо популярнее как раз из-за того, что через него можно ходить в обычную сеть.
Ну и реализация конечно пока хромает. Год назад вынужденно снял узел i2p с роутера, ибо никаким ухищрениями не удавалось добиться, чтобы он жрал меньше процессора (стоит один из первых атомов).
orignal Автор
Джава или i2pd? Для i2pd параметр precomputation.elgamal=true включает вот это, что резко снижает нагрузку на процессор.
NTCP2 же эту тормозную операцию modpow не использует совсем.
dmitryredkin
Была java. Спасибо, почитаю про i2pd.
ciphertext
Quei
Мне Tor видится как переходная технология. Просто он имеет значительное финансирование и больше разработчиков, включая ряд оплачиваемых. Он уже есть во многих прошивках роутеров и имеет свой собственный браузер. I2P просто нужно время.