Перейти к основному содержимому

Низкоуровневый ADNL

warning

Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.

Сетевой уровень абстрактных датаграмм (Abstract Datagram Network Layer - ADNL) — это основной протокол TON, который помогает сетевым одноранговым узлам взаимодействовать друг с другом.

Идентификация одноранговых узлов

Каждый одноранговый узел должен иметь по крайней мере один идентификатор, возможно, но необязательно использовать несколько. Каждая идентификация — это пара ключей, которая используется для выполнения алгоритма Диффи-Хеллмана между одноранговыми узлами. Абстрактный сетевой адрес выводится из открытого ключа следующим образом: address = SHA-256(type_id || public_key). Обратите внимание, что type_id должен быть сериализован как little-endian uint32.

Список криптосистем с открытым ключом

type_idcryptosystem
0x4813b4c6ed255191

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

Клиент-серверный протокол (ADNL через TCP)

Клиент подключается к серверу по протоколу TCP и отправляет пакет подтверждения ADNL, содержащий абстрактный адрес сервера, открытый ключ клиента и зашифрованные параметры сеанса AES-CTR, которые определяются клиентом.

Подтверждение

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

Затем клиент должен сгенерировать параметры сеанса AES-CTR, 16-байтовый nonce и 32-байтовый ключ, как для направления TX (клиент->сервер), так и для направления RX (сервер->клиент) и сериализовать их в 160-байтовый буфер следующим образом:

ПараметрРазмер
rx_key32 байта
tx_key32 байта
rx_nonce16 байт
tx_nonce16 байт
padding64 байта

Цель padding неизвестна, он не используется реализациями сервера. Рекомендуется заполнять весь 160-байтовый буфер случайными байтами, в противном случае злоумышленник может провести активную MitM-атаку, используя скомпрометированные параметры сеанса AES-CTR.

Следующий шаг — зашифровать параметры сеанса с помощью secret через протокол согласования ключей выше. Для этого AES-256 должен быть инициализирован в режиме CTR с 128-битным счетчиком big-endian с использованием пары (key, nonce), которая вычисляется следующим образом (aes_params — это 160-байтовый буфер, который был построен выше):

hash = SHA-256(aes_params)
key = secret[0..16] || hash[16..32]
nonce = hash[0..4] || secret[20..32]

После шифрования aes_params, которое отмечено как E(aes_params), AES следует удалить, поскольку он больше не нужен.

Теперь мы готовы преобразовать всю эту информацию в 256-байтный пакет подтверждения связи и отправить его на сервер:

ПараметрРазмерПримечания
receiver_address32 байтаИдентификация однорангового сервера, как описано в соответствующем разделе
sender_public32 байтаОткрытый ключ клиента
SHA-256(aes_params)32 байтаПодтверждение целостности параметров сеанса
E(aes_params)160 байтЗашифрованные параметры сеанса

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

  1. Сервер должен иметь соответствующий закрытый ключ для receiver_address, в противном случае невозможно выполнить протокол согласования ключей.
  2. SHA-256(aes_params) == SHA-256(D(E(aes_params))), в противном случае протокол согласования ключей не выполнен, и secret не одинаков на обеих сторонах.

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

Датаграмма

Как клиент, так и сервер должны инициализировать по два экземпляра AES-CTR для обоих направлений: TX и RX. AES-256 должен использоваться в режиме CTR с 128-битным счетчиком big-endian. Каждый экземпляр AES инициализируется с использованием пары (key, nonce), принадлежащей ему, которую можно взять из aes_params при обмене данными.

Чтобы отправить датаграмму, одноранговый узел (клиент или сервер) должен построить следующую структуру, зашифровать ее и отправить другому одноранговому узлу:

ПараметрРазмерПримечания
length4 байта (LE)Длина всей датаграммы, исключая поле length
nonce32 байтаСлучайное значение
bufferlength - 64 байтаФактические данные для отправки на другую сторону
hash32 байта`SHA-256(nonce \

Вся структура должна быть зашифрована с использованием соответствующего экземпляра AES (TX для клиента -> сервера, RX для сервера -> клиента).

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

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

Подробности связи

Если вы хотите углубиться в подробности связи, вы можете ознакомиться со статьей ADNL TCP - Liteserver, чтобы увидеть несколько примеров.

Вопросы безопасности

Дополнение к подключению

Неизвестно, почему первоначальная команда TON решила включить это поле в подключение. Целостность aes_params защищена хэшем SHA-256, а конфиденциальность защищена ключом, полученным из параметра secret. Вероятно, в какой-то момент планировалось перейти от AES-CTR. Для этого спецификация может быть расширена за счет включения специального магического значения в aes_params, которое будет сигнализировать о том, что одноранговый узел готов использовать обновленные примитивы. Ответ на такое подключение может быть расшифрован дважды, с новой и старой схемами, чтобы уточнить, какую схему фактически использует другой одноранговый узел.

Процесс получения ключа шифрования параметров сеанса

Если ключ шифрования получен только из параметра secret, он будет статическим, поскольку секрет является статическим. Для получения нового ключа шифрования для каждого сеанса разработчики также используют SHA-256(aes_params), который является случайным, если aes_params является случайным. Однако фактический алгоритм получения ключа с конкатенацией различных подмассивов считается вредоносным.

Nonce датаграммы

Неочевидно, почему поле nonce присутствует в датаграмме, потому что даже без него любые два шифротекста будут отличаться из-за сеансовых ключей для AES и шифрования в режиме CTR. Однако в случае отсутствующего или предсказуемого nonce можно выполнить следующую атаку. Режим шифрования CTR превращает блочные шифры, такие как AES, в потоковые шифры, что позволяет выполнять атаку с инвертированием битов. Если злоумышленник знает открытый текст, который принадлежит зашифрованной датаграмме, он может получить чистый поток ключей, выполнить XOR его со своим собственным открытым текстом и эффективно заменить сообщение, которое было отправлено одноранговым узлом. Целостность буфера защищена хешем SHA-256, но злоумышленник может подменить и его, поскольку знание полного открытого текста означает знание его хеша. Поле nonce присутствует для предотвращения такой атаки, поэтому ни один злоумышленник не может подменить SHA-256, не зная nonce.

Протокол P2P (ADNL через UDP)

Подробное описание можно найти в статье ADNL UDP - Internode.

Ссылки

Спасибо hacker-volodya за вклад в сообщество! Вот ссылка на оригинальную статью на GitHub.