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

Немного о Hidden Lake

Отличие анонимной сети Hidden Lake от других представителей данной области, по типу Tor, I2P, Mixminion, Crowds и т.п. заключается в теоретически доказуемой защите от действий глобального наблюдателя. Иными словами, даже если сеть будет небольшой, территориально ограниченной и полностью прослушиваемой, то наблюдателю будет всё также невозможно выявить хоть какие-либо связи между её участниками. Это достигается за счёт QB-задачи (задачи на базе очередей), о преимуществах и ограничениях которой более подробно можно почитать в статье тут. За счёт такой особенности, сеть Hidden Lake может успешно функционировать даже внутри централизованного сервера, сохраняя при этом тот же уровень анонимности. Об этом более подробно можно почитать здесь.

Немного о QB-задаче

Задача на базе очередей может быть описана следующим списком действий:

  1. Каждое сообщение шифруется ключом получателя,

  2. Сообщение отправляется в период = T всем участникам сети,

  3. Период T одного участника независим от периодов T1, T2, ..., Tn других участников,

  4. Если на период T сообщения не существует, то в сеть отправляется ложное сообщение без получателя,

  5. Каждый участник пытается расшифровать принятое им сообщение из сети.

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

Запускаем свой сервис

Узел в сети Hidden Lake поднять достаточно просто - достаточно лишь скачать приложение HLS и запустить его. Но в таком случае пользы от этого будет мало по двум причинам: 1) узел поднимется локально и не будет никак связан со внешней сетью, 2) будут отсутствовать прикладные приложения и, как следствие, применения. Поэтому, чтобы рассмотреть и понять процесс интеграции узла с сетью Hidden Lake от А до Я, корректнее всего будет написать собственный небольшой сервис. В нашем случае это будет echo-сервис.

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		req, err := io.ReadAll(r.Body)
		if err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		fmt.Fprintf(w, "echo(%s)", string(req))
	})
	log.Println("service is listening...")
	http.ListenAndServe("127.0.0.1:8080", nil)
}

В таком концепте нам потребуется два узла: один будет предоставлять услугу сервиса, другой будет представлен клиентом данной услуги. Для этого скачиваем приложение HLS, устанавливаем права на исполнение и копируем HLS также в директорию клиента.

$ wget https://github.com/number571/go-peer/releases/latest/download/hls_amd64_linux
$ mv hls_amd64_linux hls && chmod +x hls
$ mkdir client && cp hls client/hls

* Вышеописанный пример работает только для платформы Linux. HLS можно скачать также и для Windows / Darwin платформ, а также для arm64 архитектуры. Для этого достаточно поменять название скачиваемого файла, например: hls_arm64_darwin. Если необходимо запустить HLS на другой платформе или архитектуре процессора, тогда понадобится скомпилировать приложение уже самостоятельно.

Компиляция HLS

Код сети Hidden Lake находится полностью в открытом доступе, а потому вышеописанный способ запуска HLS не является единственным. Вместо того чтобы использовать релизные и заранее скомпилированные программы, можно скомпилировать их самим. Для этого потребуется компилятор Go и следующие действия:

$ git clone https://github.com/number571/go-peer.git
$ cd go-peer && go build ./cmd/hidden_lake/service/cmd/hls

Далее, чтобы успешно связаться с внешней сетью Hidden Lake, нам необходимо иметь в распоряжении уже настроенный конфигурационный файл hls.yml. Такие файлы можно скачать здесь. После этого нам необходимо их немного подредактировать.

Конфигурационные файлы

Взял в качестве основы конфигурационный файл в директории prod/1.

Сервисный hls.yml:

settings:
  message_size_bytes: 8192
  work_size_bits: 22
  key_size_bits: 4096
  fetch_timeout_ms: 60000
  queue_period_ms: 5000
  network_key: 8Jkl93Mdk93md1bz
logging:
- info
- warn
- erro
address:
  # Удалил tcp поле из-за ненадобности
  http: 127.0.0.1:9571 # Поменял порт, чтобы клиент мог запустить свой http сервер
services: # Удалил сторонние сервисы и прописал путь к echo-сервису
  hidden-echo-service: # Название сервиса может быть любым
    host: 127.0.0.1:8080
connections:
- 94.103.91.81:9581

Клиентский client/hls.yml:

settings:
  message_size_bytes: 8192
  work_size_bits: 22
  key_size_bits: 4096
  fetch_timeout_ms: 60000
  queue_period_ms: 5000
  network_key: 8Jkl93Mdk93md1bz
logging:
- info
- warn
- erro
address:
  # Удалил tcp поле из-за ненадобности
  http: 127.0.0.1:9572
# Удалил services поле из-за ненадобности
connections:
- 94.103.91.81:9581

После такой редакции узлы успешно смогут подключиться к ретранслятору сети, посредством которого уже смогут далее отправлять и принимать сообщения. Тем не менее Hidden Lake является также F2F сетью, а потому требует, чтобы узлы указывали публичные ключи друг друга для дальнейшей успешной и взаимной аутентификации. Чтобы сгенерировались ключи сервиса и клиента - достаточно запустить HLS приложения:

$ ./hls # Терминал 1
> [INFO] 2024/08/31 03:53:42 HLS is started; {"message_size_bytes":8192,"key_size_bits":4096,"fetch_timeout_ms":60000,"queue_period_ms":5000,"work_size_bits":22,"network_key":"8Jkl93Mdk93md1bz","limit_message_size_bytes":6526}
$ cd client && ./hls # Терминал 2
> [INFO] 2024/08/31 03:55:37 HLS is started; {"message_size_bytes":8192,"key_size_bits":4096,"fetch_timeout_ms":60000,"queue_period_ms":5000,"work_size_bits":22,"network_key":"8Jkl93Mdk93md1bz","limit_message_size_bytes":6526}

После первого запуска будут созданы приватные ключи: hls.key, client/hls.key. Для получения публичных ключей - необходимо обратиться к API каждого узла, обновить файлы hls.yml и перезапустить узлы.

$ curl http://127.0.0.1:9571/api/network/pubkey
> PubKey{3082020A...03010001}
$ curl http://127.0.0.1:9572/api/network/pubkey
> PubKey{3082020A...03010001}
Итоговые конфигурационные файлы

Итоговый сервисный hls.yml:

settings:
  message_size_bytes: 8192
  work_size_bits: 22
  key_size_bits: 4096
  fetch_timeout_ms: 60000
  queue_period_ms: 5000
  network_key: 8Jkl93Mdk93md1bz
logging:
- info
- warn
- erro
address:
  http: 127.0.0.1:9571
services:
  hidden-echo-service:
    host: 127.0.0.1:8080
connections:
- 94.103.91.81:9581
friends:
  client-node: PubKey{3082020A...03010001} # Ключ полученный от http://127.0.0.1:9572/api/network/pubkey

Итоговый клиентский client/hls.yml:

settings:
  message_size_bytes: 8192
  work_size_bits: 22
  key_size_bits: 4096
  fetch_timeout_ms: 60000
  queue_period_ms: 5000
  network_key: 8Jkl93Mdk93md1bz
logging:
- info
- warn
- erro
address:
  http: 127.0.0.1:9572
connections:
- 94.103.91.81:9581
friends:
  service-node: PubKey{3082020A...03010001} # Ключ полученный от http://127.0.0.1:9571/api/network/pubkey

В результате проделанных действий появляется возможность обратиться к сервису в сети Hidden Lake:

$ echo 'hello, world!' | base64
> aGVsbG8sIHdvcmxkIQ==
$ curl -i -X POST http://localhost:9572/api/network/request --data '{
    "receiver":"service-node",
    "req_data":{
        "method":"POST",
        "host":"hidden-echo-service",
        "path":"/echo",
        "body":"aGVsbG8sIHdvcmxkIQ=="
    }
}'
> 
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Fri, 30 Aug 2024 21:06:59 GMT
Content-Length: 102

{"code":200,"head":{"Content-Type":"text/plain; charset=utf-8"},"body":"ZWNobyhoZWxsbywgd29ybGQhKQ=="}
$ echo 'ZWNobyhoZWxsbywgd29ybGQhKQ==' | base64 -d
> echo(hello, world!)

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

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


  1. Sly_tom_cat
    30.08.2024 22:31

    А в чем суть сети из примера так и не понятно.


    1. Number571 Автор
      30.08.2024 22:31
      +1

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


      1. Sly_tom_cat
        30.08.2024 22:31

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


        1. Number571 Автор
          30.08.2024 22:31
          +2

          Возможно вы правы, т.к. при написании мной был поставлен больше упор именно на запуск узла. Добавил спойлер «Немного о QB-задаче».


  1. Sly_tom_cat
    30.08.2024 22:31
    +1

    Вот еще немного про узлы стало интересно.

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

    Как тогда строить сеть с другими (не подконтрольными) узлами?
    Да, если вы знаете IP:port узла (опять же откуда его взять?), то можете запросить у него публичный ключ и прописать его себе во френды, но кто пропишет во френды того узла ваш pb key?

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

    Допускаю что эти моменты как-то освещены в ссылках на подробности, но там довольно объемные документы (как я уже говорил).


    1. Number571 Автор
      30.08.2024 22:31

      В сети Hidden Lake существует фактически два уровня маршрутизации: сетевой и криптографический.

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

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

      Да, если вы знаете IP:port узла (опять же откуда его взять?), то можете запросить у него публичный ключ и прописать его себе во френды, но кто пропишет во френды того узла ваш pb key?

      Обмен публичными ключами и дальнешая их установка должна осуществляться вне сети Hidden Lake. Это может быть как физический обмен ключами, так и обмен ключами посредством например такого протокола. В HL можно также и отключить опцию F2F, прописав в настройках конфига строчку f2f_disabled: true, но я бы не стал такого делать из-за возможности спама, а также возможных активных наблюдений за состоянием очереди, хоть это в сумме и упростит дальнейшую коммуникацию.

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

      Подключаться можно к ретрансляторам или к уже существующим узлам с внешним адресом. Все такие подключения будут происходить на сетевом уровне, а потому выстраиваемая топология никак не будет приводить к раскрытию информации об общении узлов. Наблюдателю будет доступна лишь информация об участниках сети, т.е. информация об их факте подключения к сети. Информация же о каком-либо факте отправления или получения сообщений будет для него недоступна. Если необходимо скрывать также и принадлежность участников к сети, то можно это сделать посредством сокрытия / инкапсуляции анонимизированного трафика в HTTPS (адаптеры), выстраиванием динамических периодов (rand_queue_period_ms>0) и размеров сообщений (rand_message_size_bytes>0).


  1. Sly_tom_cat
    30.08.2024 22:31

    Хм... получается я могу и вообще не иметь ни одного френда, но подключится к сети через ретранслятор....

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

    Правильно я понимаю, что ретранслятор примет сообщение от вообще любого узла?
    Ретранслятор, как я понял, на одно сообщение должен послать N сообщений другим узлам. Ппри этом он не может знать - что там в сообщении т.к. все зашифровано, а у него нет ключей. И даже есть ли у узла приславшего ему сообщение френды - он тоже знать не может. Тогда и отличить спам от полезный сообщений - нет возможности. Так это прямо таки лакомый кусочек для DOS или DDOS атак. Положить узел, который из одного сообщения делает N - проще простого. И защиты никакой не сделать, на первый взгляд.


    1. Number571 Автор
      30.08.2024 22:31

      Хорошо, ретранслятор - а как его найти в сети? Должен быть тогда какой-то список...

      Да, конфигурационные файлы с указанными ретрансляторами можно найти тут: _configs/prod. Есть также таблица ретрансляторов с более подробной информацией внизу файла README.

      Правильно я понимаю, что ретранслятор примет сообщение от вообще любого узла?

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

      Hidden Lake, основываясь на QB-задаче, также не решает проблему масштабируемости окончательно, поэтому как вы полностью верно указали, она может подвергаться DoS и DDoS атакам. Для того, чтобы такие атаки сделать менее влиятельными в Hidden Lake предусмотрены следующие моменты:

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

      2. Для того, чтобы сети с разными network_key параметрами проблематично сливались в одну систему, используется алгоритм доказательства работы (PoW),

      3. Параметр network_key может быть секретным значением. В таком случае преднамеренные атаки отказа в обслуживании становятся наименее вероятными.

      Если это всё переносить на Hidden Lake в открытой сети, то безусловно network_key параметр не будет являться секретным, поэтому третий пункт отпадает. В HL существует четыре ретранслятора, каждый из которых с разным network_key. В таком случае, для последующего успешного общения должен быть выбран один из них.


      1. NemoVors
        30.08.2024 22:31

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


        1. Number571 Автор
          30.08.2024 22:31

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


  1. Number571 Автор
    30.08.2024 22:31

    del