Invisible Internet Project (I2P, проект невидимого интернета) – лидер среди технологий приватной передачи информации. Полная децентрализация и независимость делают сеть I2P архитектурно сложной, но уникальной в своем роде. Статья посвящена вопросу: может ли программист, несведущий в криптографии и сетях написать приложение, работающее через I2P.
Современные реализации I2P-роутеров имеют поддержку API-протокола SAM (Simple Anonymous Messaging), который позволяет внешним приложениям общаться через I2P, используя всего несколько простых команд. В рамках этой статьи рассмотрим необходимый минимум для начала собственных экспериментов.
![](https://habrastorage.org/getpro/habr/upload_files/6c0/c76/ab8/6c0c76ab8dcadc4c38b41038463234c0.png)
Небольшая ретроспектива
История I2P начинается в первой половине 2000-ых. Хорошие практики разработки ПО в эпоху видеокассет распространялись неактивно. Представить в то время современные концепции взаимодействия с внешними приложениями через удобный интерфейс (API) было сложно.
Изначально программный клиент сети предоставлял во внешний мир только прокси. Прокси, по-простому говоря, является посредником, который принимает запрос, например, «example.i2p», и обрабатывает его. Для внешнего приложения вся внутренняя логика абсолютно скрыта, а сам процесс напоминает использование обычной сети.
Неискушенный читатель наверняка не видит в этом проблему, ведь «есть прокси – делай что хочешь»! Отчасти это так – клиент-серверные приложения будут работать в штатном режиме: сайт откроется, IRC-чат тоже. Но что делать с peer-to-peer технологиями и как динамически создавать новые идентификаторы пользователя, чтобы все приложения не серфили сеть от одного лица?
В I2P-роутере существует протокол I2CP (I2P Control Protocol), который призван логически разделить сущность роутера анонимной сети и внешнее приложение. Однако не все так радужно, как показалось команде разработчиков в начале нулевых: I2CP является очень громоздким и сложным протоколом. Чтобы внешнее приложение могло делать хоть что-то осмысленное, оно должно содержать больше половины кода роутера. Представьте, что для покупки в интернет-магазине вам нужно владеть хотя бы разговорным китайским!
Сложность интеграции I2P во внешнюю программу весьма неплохо объясняет перегруженность исконного Java-роутера всевозможными дополнительными приложениями вроде BitTorrent и Email, которые превратили I2P-роутер из небольшой утилиты в монолитного мамонта.
Актуальному API-протоколу SAM предшествовал BOB (Basic Open Bridge). Когда сеть I2P глобально перешла на более совершенную криптографию (отказ от алгоритма SHA1), разработчики флагманского Java-роутера решили оставить Боба в прошлом и не учить его новым стандартам. Как бы там ни было, тратить и без того скудные ресурсы на одновременное сопровождение двух похожих протоколов показалось нецелесообразно.
Перед тем, как перейти к основной части статьи отмечу, что в реализации роутера на C++ (i2pd) BOB является актуальным протоколом наравне с SAM. Команда PurpleI2P не дает ему погибнуть, при необходимости актуализируя кодовую (и функциональную) базу. Если загоритесь энтузиазмом, можете с ним ознакомиться.
Начало урока
Существует несколько библиотек для разных языков программирования, предоставляющие готовые функции и методы для взаимодействия с I2P-роутером через SAM. Их официальный перечень легко найти на странице документации. Если удачно напишете свою библиотечку, подумайте о ее добавлении в этот список.
Разберем принципы работы с SAM на самом общем примере – через терминал. Для примера используются утилита netcat и роутер i2pd (Java-роутер будет работать аналогичным образом). Старый добрый telnet для подключения к Сэму не подойдет, потому что использует два символа завершения строки (\r\n
), а SAM обрабатывает конец сообщения по символу \n
. Символ \r
не предусмотрен протоколом, поэтому считается ошибкой, из-за чего происходит остановка работы.
В i2pd SAM по умолчанию, как правило, включен. Чтобы убедиться в этом, откройте веб-интерфейс (http://127.0.0.1:7070
), в котором на главной странице отображается статус сервисов. Если SAM все-таки неактивен, включите его через конфигурационный файл параметром sam.enabled = true
. По умолчанию он доступен для обращений по адресу 127.0.0.1:7656
.
![](https://habrastorage.org/getpro/habr/upload_files/fb0/5f3/217/fb05f321726ec39fbe2a4e7ab39013db.png)
Hello hidden world
При подключении к сокету, который слушает служба SAM, нужно инициировать рукопожатие (handshake). Для этого необходимо написать HELLO VERSION
. В ответ роутер возвращает свою версию протокола SAM. Если RESULT=OK
, рукопожатие прошло успешно. Также в команду HELLO VERSION
можно добавить требование к версии: HELLO VERSION MIN=3.0 MAX=3.3
. Это пригодится в случае использования новых возможностей, о которых старые роутеры не знают. Практика многих людей показывает, что версии 3.0 достаточно для реализации самых различных задач.
![](https://habrastorage.org/getpro/habr/upload_files/421/1cd/dbf/4211cddbf62d4a8a88cc2bda7fcc9c7d.png)
При создании сессии необходимо указать ее никнейм (id), который мы будем использовать в дальнейшем для подключения к сессии, а также ключи, которые будут являться криптографическими идентификатором новой скрытой точки (destination). Главное требование к никнейму – отсутствие пробелов, а вот с ключами вопрос несколько интереснее. Если указать значение TRANSIENT
, создадутся случайные ключи, которые после создания сессии будут выданы монолитным массивом информации (в кодировке base64), в который одновременно входят публичные и приватные ключи. Так как размер части с публичными и приватными ключами заранее известен, при программной реализации не составляет труда раскодировать информацию и отделить публичные ключи от приватных (например, чтобы их можно было сообщить собеседнику). Для полноты алгоритма действий, а также для простоты использования SAM через терминал с ручным копированием ключей, создам ключи отдельной командой перед созданием сессии.
Команда DEST GENERATE
может быть использована в таком виде, либо с явным указанием типа подписи. По умолчаниюSIGNATURE_TYPE=0
- устаревший тип, сейчас рекомендуется использовать 7
, но чтобы не усложнять восприятие, воспользуемся типом подписи по умолчанию, то есть не будем его указывать. О типах подписи не обязательно знать для базового использования I2P.
![](https://habrastorage.org/getpro/habr/upload_files/569/cdf/79a/569cdf79a06cae26e8e7daa69f1eaad1.png)
После получения команды, роутер возвращает ключи с явным разделением публичных (PUB) и приватных (PRIV). Это весьма удобно при использовании простого парсинга по ключевым словам.
Теперь переходим непосредственно к созданию сессии. SAM поддерживает три типа сессий: STREAM (для TCP), DATAGRAM (отправка и получение пакетов UDP) и RAW (вариация на тему UDP). Наиболее наглядным и базовым протоколом является TCP. Его используют для основных операций вроде передачи файлов и сообщений, поэтому для примера создадим STREAM-сессию.
SESSION CREATE STYLE=STREAM ID=HabraHabr DESTINATION=БЛОК_ПРИВАТНЫХ_КЛЮЧЕЙ
Параметр STYLE
отвечает за тип сессии (в нашем случае STREAM
), DESTINATION
– принимает приватные ключи (блок информации из секции PRIV
недавно сгенерированных ключей), ID
– никнейм сессии, по которому можно к ней обратиться (в примере: HabraHabr
).
![](https://habrastorage.org/getpro/habr/upload_files/bf0/22d/726/bf022d7263ccf9ffc41a85f5a140d026.png)
Через несколько секунд роутер сообщает об успешном создании сессии. Сессия будет жить пока сокет, который ее создал, существует. При этом работать с сессией необходимо через новые подключения к Сэму. Это значит, что контрольный сокет хоть и не используется напрямую, но должен существовать до тех пор, пока нам нужна конкретная сессия.
Через новое окно снова подключаюсь к Сэму и произвожу стандартное рукопожатие. Есть два пути использования существующей сессии «HabraHabr»: инициировать соединение с другим скрытым узлом от лица этой сессии, зная его адрес, либо подключиться к локальному стриму для ожидания входящего соединения извне. Чтобы принять сообщения, которые будут отправлены из второй сессии, подключимся к стриму командой STREAM ACCEPT ID=HabraHabr
.
![](https://habrastorage.org/getpro/habr/upload_files/d76/a15/4ac/d76a154ac689e875e96ce0dd2ab6f82e.png)
С первой сессией закончено. Открываю новое окно терминала, подключаюсь к Сэму и создаю новую сессию «Novosibirsk» лишь с той разницей, что не генерирую ключи отдельно, а указываю TRANSIENT
в параметре DESTINATION
. Суть происходящего от этого не меняется.
![](https://habrastorage.org/getpro/habr/upload_files/7ea/c0c/ae7/7eac0cae7cf8bfea52293ba108cbb1d5.png)
Чтобы подключиться к предыдущей сессии «HabraHabr», открываю еще одно окно терминала, подключаюсь к Сэму и ввожу команду:
STREAM CONNECT ID=Novosibirsk DESTINATION=ПУБЛИЧНЫЕ_КЛЮЧИ_СЕССИИ_HabraHabr
![](https://habrastorage.org/getpro/habr/upload_files/ec8/78b/14f/ec878b14fa0ba17c195805f51505a3a4.png)
При входящем сообщении, на сокет приходит блок публичных ключей подключенного абонента, который является его внутрисетевым идентификатором (на скриншоте слева). В текущей сессии эта информация не нужна, но она может пригодиться, чтобы позже установить соединение с абонентом по нашей инициативе.
![](https://habrastorage.org/getpro/habr/upload_files/b51/60c/7ff/b5160c7ffdf22eb53c1187956ddaf279.png)
Таким образом происходит соединение двух конечных точек. Несмотря на то, что фактически они расположены на одном I2P-роутере, соединение проходит через несколько компьютеров по всему миру, причем соединение в каждую сторону осуществляется через отдельную цепочку транзитных узлов. Иногда это наглядно прослеживается при быстрой отправке сообщений из одного окна терминала в другое и при долгой доставке в обратном направлении.
То, что показано в примере, как примитивный текстовый чат, по своей сути является прямым подключением двух абонентов через скрытую сеть. Подобное P2P (пир-ту-пир) соединение можно использовать для общения по самым различным протоколам без использования сервера-посредника и сохраняя конфиденциальность обеих сторон.
![](https://habrastorage.org/getpro/habr/upload_files/6c1/87c/948/6c187c948aeebb5b431e83cb7c3fda7c.png)
Когда один из собеседников прервет соединение, пользователь на другом конце также отключится от своей сессии. Манипуляции собеседника на другом конце провода не влияют на локальную сессию, но каждый раз к ней необходимо переподключаться с нового сокета, чтобы поймать новое входящее соединение. Можно сказать, каждое подключение является одноразовым. В случае примера с терминалами это означает, что для каждого соединения необходимо новое окно терминала с подключением к Сэму в режиме ожидания.
Это особенно важно иметь ввиду при практической реализации: необходимо создавать новое подключение к локальной сессии каждый раз, когда существующее приняло входящее соединение. Также возможно одновременное наличие нескольких ожидающих сокетов, что исключит отказ в соединении для пользователя, для которого мы не успели создать новое ожидающее соединение.
![](https://habrastorage.org/getpro/habr/upload_files/072/08d/948/07208d94887a337a6fc6897ead6aa459.png)
Резюме
Данная статья демонстрирует основные принципы работы, которую ваше приложение может выполнять непосредственно, либо используя обертки этих же команд в виде библиотеки. Если тема окажется востребованной, разберем SAM более подробно.