Анонимность участников сети I2P достигается путем использования туннелей. Важная особенность I2P заключается в том, что длину туннеля, его начало и конец знает только тот, кто его создал.

Каждый узел сети имеет свои входящие и исходящие туннели, при этом по умолчанию он также выступает в роли транзитного узла в цепочках других участников. Туннели I2P являются однонаправленными - по каждому туннелю трафик идет только в одну сторону. В анонимной сети пользователь не имеет прямого доступа к абоненту на другой стороне, в распоряжении лишь информация о начальном узле в его входящем туннеле. Обращение к входящему туннелю другого участника сети происходит через анонимизирующую цепочку со своей стороны - через исходящий туннель. После установления контакта, адресату сообщается для ответа информация о входящем туннеле обратившегося к нему участника сети. О том, как узлы находят входящие туннели друг друга для первого обращения, читайте в статье про флудфилы.

Цепочки из нескольких узлов - это, если не самая основная, то одна из важнейших логических частей I2P. В рамках данной статьи растолкуем принцип их построения и умозрительно докажем, что уровню анонимности в I2P можно доверять.

Транспорты

Ходовые низкоуровневые сетевые протоколы на сегодняшний день это TCP и UDP. Они обеспечивают логику доставки информации между абонентами, но абсолютно никак не отвечают за приватность этой информации - ее может перехватить по пути следования любой желающий. Ввиду этого для более высокоуровневых протоколов прикладных программ возникает необходимость в шифровании информации.

В работе I2P используются транспортные протоколы NTCP2 - аналог TCP, и SSU - аналог UDP. По сути дела, эти протоколы являются криптографическими обертками своих старших братьев. Информация в рамках "невидимого интернета" передается по этим протоколам, что позволяет скрыть всю передаваемую информацию от домашнего провайдера.

NTCP2 и SSU не просто шифруют информацию, но еще и примешивают случайное количество лишних байт. "Мусор" не оставляет шанса системам анализа трафика, так как размер пакетов является случайным и фактически ни о чем не говорит. После расшифровки пакета в пункте назначения холостые байты просто откидываются.

NTCP2 и SSU являются инструментами для безопасного соединения peer-to-peer - прямой связи с другими роутерами сети. В начале этого материала сказано, что анонимность внутри I2P достигается за счет туннелей, состоящих из нескольких промежуточных узлов. Ввиду этого не будем останавливаться на том, как узлы общаются между собой напрямую, а пойдем ближе к теме анонимизирующих туннелей. Подробно о транспортном протоколе NTCP2 на русском языке читайте тут.

Что есть туннель

I2P-роутеры, то есть все узлы сети, общаются между собой по протоколу I2NP. Фактический перечень типов сообщений I2NP можно увидеть в исходном коде, либо обратиться к официальной документации за детальным объяснением.

По сути дела, I2NP содержит исчерпывающий набор типов возможных сообщений, необходимых для общения I2P-роутеров между собой. Обратите внимание на типы, имена которых содержат слово Tunnel. Именно они интересны нам в рамках этой статьи. Вся пользовательская информация передается внутри туннелей в виде зашифрованных блоков от роутера к роутеру, благодаря чему изначальный источник информации теряется, а ее настоящий получатель остается неизвестным, так как неизвестно на каком из транзитных роутеров туннель прерывается.

К логике туннелей предъявляется два основных требования: анонимность создателя туннеля перед его участниками, а также совместимость транзитных узлов цепочки по транспортам. Кратко про построение туннелей сказано в большой статье про I2P, однако сейчас мы углубимся в недра этого сложного механизма и еще раз убедимся в том, что уровень анонимности в I2P является беспрецедентным в своем роде.

Аналогия архитектуры I2P с обычной сетью

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

На иллюстрации принципиально схожие уровни обычной сети TCP/IP и сети I2P находятся напротив друг друга - на одной строке
На иллюстрации принципиально схожие уровни обычной сети TCP/IP и сети I2P находятся напротив друг друга - на одной строке

Ваше устройство подключается к интернету через маршрутизатор (роутер). Это может быть домашний роутер с подключением через провод или Wi-Fi, либо роутер сотового оператора с подключением через сети 3G/4G/5G. Суть от этого не меняется. Перед тем, как установить логическое IP-соединение с роутером, устройство обменивается служебной информацией на низком уровне - на иллюстрации он обозначен, как "Ethernet-фреймы" (упрощенно). После установки логической связи по протоколу IP с роутером, вы можете обращаться через него к компьютерам по всему миру. Когда вы открываете сайт, сервер которого находится в соседнем городе, ваш трафик проходит через множество транзитных узлов интернет-провайдеров, при этом у вас остается прямое сообщение только с изначальным маршрутизатором. Все промежуточные узлы работают с вами на уровне IP-соединений - передают некие пакеты информации из точки А в точку Б. Каждый логический агрегат по пути следования вашего трафика строит IP-соединение со своими соседями также на более низком уровне, как вы и ваш домашний роутер. Получается, что множество компьютеров и роутеров по всему миру устанавливают низкоуровневую связь со своими физическими соседями через провода (или другие среды передачи информации) и, основываясь на этой связи, объединяются в IP-сети, где царствует привычная глазу маршрутизация с IPv4 или IPv6 адресами. Самый высокий уровень связи TCP/IP обозначен на иллюстрации, как "прикладной". Это вершина пирамиды - пользовательский трафик, например, запрос браузера на сайт по протоколу HTTPS. Более низкие уровни ничего не знают про ваш сайт и браузер, они просто работают на то, чтобы передать некую бинарную информацию в пункт назначения, где она будет прочитана в изначальном виде и обработана.

I2P работает поверх TCP/IP, но имеет свою дополнительную структуру, которая отчасти повторяет обычную сеть, но с сильным уклоном в приватность и анонимность. I2P-роутер - программный клиент на вашем устройстве, который обеспечивает всю внутреннюю логику сети. Сообщения I2NP являются базовым инструментом общения I2P-роутеров между собой. Туннели являются аналогом IP-соединений, которые обеспечивают прямое взаимодействие с другими роутерами и через них взаимодействие с узлами, прямой контакт с которыми отсутствует. Высший уровень на иллюстрации - "чесночное сообщение" - это пользовательская информация, доставляемая по туннелям, но имеющая смысл только для конечного получателя, который может ее расшифровать.

Сообщения скрытой сети проходят через обычный интернет, но их содержимое может быть осмыслено только на уровне выше, для выхода на который нужен I2P-роутер. Это свойство называется оверлейностью. Если вдаваться в формализм, термин "глубинный интернет" в отношении I2P и других скрытых сетей должен звучать противоположно, например, "интернет высокого уровня".

Чесночное сообщение, образующее туннель

Каждый I2P-роутер публикует информацию о себе на флудфилах - справочных узлах сети. Полный адрес роутера называется "Router Info", или просто "RI". Помимо информации о непосредственной доступности (IP-адресы и адреса интродьюсеров), а также некоторой служебной информации, RI содержит публичный ключ шифрования роутера.

Локальная база сети I2P-роутера полностью состоит из файлов RI других участников сети. Когда роутеру необходимо обзавестись туннелем, первым делом выбираются его будущие участники. Поиск кандидатов происходит в локальной базе сети (netDb). Учитываются заявленная пропускная способность роутера, транспортная совместимость с соседями, а также данные из профиля, если он имеется. Под профилированием подразумевается локальная хроника роутера о взаимодействии с конкретным участником сети и оценка его стабильности - нестабильный роутер не будет использован при построении туннеля.

Когда кандидаты выбраны, создается чесночное сообщение. Если строится туннель не длиннее четырех хопов (прыжков), чеснок всегда содержит четыре зубчика, то есть четыре сообщения. В противном случае чеснок состоит из восьми сообщений (по количеству максимально возможной длины туннеля). По умолчанию туннели имеют длину в три транзитных узла.

Принцип чесночного сообщения заключается в том, что каждый участник туннеля получает полный набор сообщений (чеснок), но может прочитать только один зубчик, предназначенный ему. Затем чесночное сообщение целиком передается дальше согласно полученных в зубчике инструкций. Зубчики опознаются получателями по первым 16 байтам, которые являются началом хеша от их ключа шифрования. После прочтения чесночного сообщения, участник заменяет содержимое зубчика на свой ответ. Чтобы информация осталась в тайне, она шифруется симметричным ключом.

Занимательнейший нюанс заключается в том, что конкретный узел может увидеть лишь свой зубчик в чесноке. Это не позволяет строить догадки о других участниках туннеля по хешам их ключей. Прием обеспечивается дополнительным симметричным шифрованием всего чеснока. Возможно, когда-нибудь об этом будет отдельная заметка, но вот исходный код для самых смелых:

libi2pd/Tunnel.cpp
hop = m_Config->GetLastHop ()->prev;
		while (hop)
		{
			// decrypt records after current hop
			TunnelHopConfig * hop1 = hop->next;
			while (hop1)
			{
				hop->DecryptRecord (records, hop1->recordIndex);
				hop1 = hop1->next;
			}
			hop = hop->prev;
		}
		while (hop)
		{
			// decrypt current hop
			if (hop->recordIndex >= 0 && hop->recordIndex < msg[0])
			{
				if (!hop->DecryptBuildResponseRecord (msg + 1))
					return false;
			}	
			else
			{
				LogPrint (eLogWarning, "Tunnel: hop index ", hop->recordIndex, " is out of range");
				return false;
			}	
			
			// decrypt records before current hop 
			TunnelHopConfig * hop1 = hop->prev;
			while (hop1)
			{
				auto idx = hop1->recordIndex;
				if (idx >= 0 && idx < msg[0])
					hop->DecryptRecord (msg + 1, idx);
				else
					LogPrint (eLogWarning, "Tunnel: hop index ", idx, " is out of range");
				hop1 = hop1->prev;
			}
			hop = hop->prev;
		}

На сегодняшний день чесночные сообщения бывают трех типов и имеют соответствующие отличия:

  • Старый. Используется, когда все транзитные роутеры в туннеле используют шифрование El Gamal. Каждое сообщение составляет 528 байт и содержит ключи AES: одноразовый - для шифрования ответа, вектор инициализации (IV), ключ шифрования IV и основной ключ, используемый в течение жизни туннеля для луковичного (многослойного симметричного) шифрования.

  • Переходный. Используется, когда среди транзитных роутеров есть узлы как со старым шифрованием El Gamal, так и новые с ECIES. Каждое сообщение составляет 528 байт, но для ECIES-узлов используется другое шифрование для ответа: ключ симметричного шифрования в непосредственном виде не передается, а вычисляется по протоколу Noise (Noise_N).

  • Новый - "чеснок с короткими сообщениями". Используется, когда все транзитные роутеры используют шифрование ECIES и имеют версию не ниже 2.39.0, если речь про i2pd, или 1.5.0 для Java-роутера. Каждое сообщение составляет 218 байт. Зубчики не содержат вышеупомянутые ключи, потому что в данном случае все они являются вычисляемыми. AES используется только в качестве основного ключа шифрования для туннеля, в остальном используются алгоритмы AEAD/Chaha20/Poly1305 и ChaCha20. Подробнее о коротких чесночных сообщениях читайте в спецификации.

Уникальность нового типа чесночных сообщений заключается в его размере - стандартный чеснок из четырех зубчиков умещается в один килобайт. На момент публикации статьи все туннельные сообщения сети I2P разделяются на фрагменты по одному килобайту. Это стандартный размер, который является элементом борьбы с анализом трафика.

Чеснок минимального размера с зубчиками по 528 байт требует отправки трех туннельных сообщений. Несмотря на обфускацию трафика, гипотетически можно выявить закономерность: три килобайтовых сообщения - это стандартный чеснок. Если видим характерное движение трафика, значит, наблюдаемый пользователь строит туннель, либо является транзитным звеном в туннеле другого пользователя. На практике эта угроза из разряда фантастики, потому что туннели живут всего по десять минут, а активные узлы постоянно участвую более, чем в двух тысячах транзитных туннелей. Можно представить сколько чесноков на подобном роутере пролетает лишь в течение минуты...

При использовании коротких сообщений, весь чеснок умещается в одно туннельное сообщение, поэтому новый чеснок абсолютно сливается с остальным потоком информации. Теряется последняя надежда шпиона на анализ трафика на предмет трех килобайтовых сообщений подряд. Но не всё о паранойе, дамы и господа! Одно туннельное сообщение вместо трех - это фактор увеличения скорости построения туннеля: меньше пакетов идет - меньше вероятность того, что какой-то из них может потеряться, да и передается меньший объем информации быстрее.

Исходящие и входящие туннели строятся схожим образом:

  • При построении входящего туннеля, чтобы остаться инкогнито, роутер-создатель передает чеснок первому участнику через свой исходящий туннель. Затем чеснок передается от узла к узлу и приходит обратно к создателю туннеля. Для последнего участника цепочки создатель не отличается от очередного транзитного узла.

  • При построении исходящего туннеля картина обратная: роутер-создатель передает чеснок напрямую ближайшему по цепочке роутеру, который не может знать является ли роутер, передавший чеснок, хозяином чеснока, или таким же транзитным узлом. Зубчик последнего транзитного узла содержит информацию для отправки чеснока обратно создателю. Пройдя всю цепочку, чеснок возвращается создателю через один из входящих туннелей.

При старте I2P-роутер не имеет настоящих туннелей, только два туннеля нулевой длины. При создании первых полноценных туннелей необходимые обращения происходят не через анонимизирующие цепочки узлов, а напрямую - через туннель нулевой длины. В силу того, что это редкое событие и обращение через туннель нулевой длины неотличим от нормального, эта особенность не считается слабым местом архитектуры I2P.

Туннели в практическом контексте сети

Зубчики чеснока помимо ключей шифрования содержат информацию о номерах туннелей (каждый - 4 байта). Один номер - это номер туннеля, на который роутер будет принимать сообщения, а второй номер - это номер туннеля на следующем транзитном узле, адрес которого прилагается, куда текущий транзитный роутер будет передавать поступающую информацию.

Чесночные сообщения содержат лишь краткие адреса роутеров, которым нужно дальше по цепочке передать чеснок. Адрес роутера является хешем SHA256 от его полного Router Info. Если нужного роутера нет в локальной базе, транзитному узлу необходимо обратиться к флудфилу, чтобы получить его полный адрес (RI). При составлении чеснока, создателем туннеля учитывается транспортная совместимость соседей между собой, что снижает вероятность неудачной попытки построения туннеля. Например, глупо просить роутер без IPv6-адреса связаться с участником сети, у которого есть только IPv6.

Туннель считается неудавшимся, если ответ долго не приходит, либо чеснок вернулся, но кто-то из опрошенных роутеров в свой ответ заложил отказ от участия (это может быть связано с лимитом на количество транзитных туннелей и прочим).

Когда туннель создан, его жизненный цикл ограничивается десятью минутами. После этого времени все транзитные узлы перестают принимать пакеты в рамках старого туннеля, а его создателю к этому времени необходимо иметь уже новый туннель - он создается незадолго до "протухания" старого. После обновления входящих туннелей, если речь идет о серверной конечной точке, которая ждет обращения от других участников сети, также обновляется ее лизсет - контактная информация на флудфилах, которая включает в себя сведения о входящих туннелях.

Как таковое чесночное шифрование используется в I2P только при создании туннелей. В рабочем режиме в туннеле используется только луковичное шифрование (плюс сквозное шифрование от пользователя до пользователя).

Луковичное шифрование - это термин, означающий многослойное шифрование симметричным ключом. Если кто-то забыл или не знает: при симметричном шифровании шифрование и расшифровка осуществляются одним ключом, в отличие от асимметричных алгоритмов, где шифрование происходит публичным ключом, а расшифровка - приватным. Асимметричное шифрование применяется при сквозном шифровании.

Все туннели являются однонаправленными, при этом транзитные узлы не знают ничего: является ли туннель входящим или исходящим, сколько в нем участников и так далее. Их задача заключается в шифровании проходящей информации полученным (или выведенным математически) симметричным ключом и передача получившегося пакета следующему узлу.

Особые роли принимают лишь конечные узлы в туннелях: для исходящего туннеля последний узел является Endpoint, а первый узел во входящем туннеле - Gateway. В отличие от обычных "серединных" узлов, эти двое знают о своем месте в цепочке благодаря специальным флагам, которые они получают в чесноке. Задача Endpoint заключается в сборе килобайтных туннельных сообщений в более весомый пакет (до 64 килобайт) и его передача дальше согласно инструкций (во входящий туннель, напрямую другому роутеру, либо локальная обработка информации). Задача Gateway обратная: разбивать полученные сообщения на стандартные фрагменты по одному килобайту и отправлять эти фрагменты дальше по входящему туннелю.

При отправке пакета, роутер поочередно расшифровывает его всеми ключами транзитных узлов. Это делается, чтобы после того, как каждый транзитный узел зашифрует информацию, она оказалась в исходном виде. Это особенность симметричных алгоритмов шифрования AES и ChaCha20. Звучит немного сложно, но суть в том, что шифрование и расшифровка - это зеркальные операции и при расшифровке исходной информации она шифруется, но чтобы ее потом расшифровать нужно провести операцию в обратном направлении, то есть зашифровать.

На последнем роутере исходящего туннеля - Endpoint - снимается последний слой луковичного шифрования и информация передается во входящий туннель другой стороны. Несмотря на то, что в этом месте всё луковичное шифрование снято, пользовательская информация не подвержена угрозе, так как к ней в первую очередь применяется сквозное шифрование.

Во входящем туннеле всё более просто для ума непосвященного человека: каждый транзитный узел добавляет шифрование своим ключом (и это действительно просто новый слой шифрования), затем передает получившийся пакет следующему узлу. Конечный получатель, сняв все луковичное шифрование, подвергает информацию расшифровке своим асимметричным ключом и проводит проверку подписи, после чего расшифрованная информация поднимается на уровень выше и отдается внешнему (прикладному) приложению в исходном виде.

В общих чертах завершенная схема передачи пользовательских данных выглядит, как показано на иллюстрации.

При исходящем туннеле:

  • I2P-роутер принимает информацию от внешнего локального приложения, упаковывает ее в I2NP Data (gzip + служебные заголовки);

  • Осуществляется сквозное шифрование ключом получателя (ключ из лизсета), получается I2NP Garlic;

  • Информация подготавливается для отправки по конкретному туннелю, образуется сообщение I2NP Tunnel;

  • Трафик оборачивается в криптографию транспортного протокола;

  • Информация уходит по физической сети первому (ближайшему) узлу исходящего туннеля.

При входящем туннеле:

  • По сети приходят пакеты, предназначенные I2P-роутеру;

  • Происходит обработка транспортных протоколов, расшифровка I2NP-сообщений;

  • Сообщение является I2NP Tunnel - туннельным сообщением;

  • По идентификатору туннеля роутер понимает, что данное сообщение пришло из его туннеля. Снимается луковичное шифрование;

  • Имеющееся сообщение типа I2NP Garlic - это исходное сообщение отправителя, зашифрованное асимметричным ключом получателя. Роутер, являясь держателем адреса, который является получателем данного сообщения, расшифровывает его приватным ключом адреса (на самом деле используются одноразовые ключи и прочее, но пощадите нашу с вами психику!);

  • На выходе получается I2NP Data - пользовательская информация (gzip + служебные заголовки), которая распаковывается и отдается внешнему приложению.

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

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


Это упрощенное объяснение работы туннелей I2P, но вопреки всем усилиям по упрощению материал оказался весьма сложным для восприятия. Как бы то ни было, самое главное, я считаю, это наличие хотя бы какого-то понимания технологии, на которую вы решитесь положиться в решении важных задач.

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


  1. RiddickABSent
    02.09.2021 23:17

    Доходчивое объяснение для тех, кому не достаточно в стиле: "WireGuard - потому что потому".


  1. cyberdazine
    03.09.2021 06:56

    Как обычно информативно и понятно, спасибо!


  1. lartie
    03.09.2021 13:58

    Спасибо, отличный материал, захотелось и дальше погрузиться в тему.


  1. Thisnickname2019
    06.09.2021 08:35

    Всё жду энтузиастов, тех кто перепишет всё на го.


    1. pureacetone Автор
      07.09.2021 19:33

      Заходите в http://irc.ilita.i2p. Иногда проскакивают обсуждения про реализацию I2P на JS. В угоду хайпа и уровню нынешних программистов))


  1. loskiq
    06.09.2021 19:16

    неизвестно когда будет ssu2?


    1. pureacetone Автор
      06.09.2021 19:17
      -1

      может быть @orignalзнает))