Слогер Создать блог
Разработка

XChaCha20-Poly1305 в mesh-сетях: практический разбор

Разбираем, почему в децентрализованных сетях выбирают XChaCha20-Poly1305 вместо привычного AES, как работает шифрование и обеспечивается прямая секретность.

Большинство руководств по шифрованию сводятся к совету «используй AES-256-GCM». Но в mesh-сети угрозы иные: ключи постоянно меняются, сообщения приходят не по порядку, а некоторые узлы могут быть скомпрометированы. Нужен AEAD-шифр, который быстр, портативен и хорошо сочетается с выводом ключей.

GhostWire использует XChaCha20-Poly1305 в качестве основного шифра. Вот почему и как это работает на практике.

Почему не AES-256-GCM?

AES быстр на устройствах с аппаратным ускорением AES-NI. Но GhostWire работает на ESP32, Raspberry Pi, старых ноутбуках и телефонах — не везде есть аппаратный AES.

ChaCha20-Poly1305:

  • Постоянное время выполнения на всех платформах — нет уязвимостей по сторонним каналам.
  • Быстр в программной реализации — часто обгоняет AES на устройствах без AES-NI.
  • Проще реализация — меньше шансов ошибиться.

XChaCha20-Poly1305 расширяет стандартный вариант, используя 192-битный nonce (против 96 бит у обычного ChaCha20-Poly1305). Это критично для mesh-сетей, где на одном ключе могут шифроваться миллионы сообщений — 96-битный nonce исчерпался бы после ~2^32 сообщений.

Процесс шифрования

Вот как происходит шифрование сообщения в GhostWire:

use chacha20poly1305::{XChaCha20Poly1305, KeyInit, aead::Aead}; use chacha20poly1305::aead::OsRng; use x25519_dalek::{EphemeralSecret, PublicKey}; // 1. Обмен ключами (X25519 ECDH) let secret = EphemeralSecret::random_from_rng(OsRng); let public = PublicKey::from(&secret); // 2. Вычисление общего секрета let shared = secret.diffie_hellman(&peer_public); // 3. Вывод ключа шифрования из общего секрета let key = hkdf_expand(shared.as_bytes(), b"ghostwire-v1"); // 4. Шифрование XChaCha20-Poly1305 let cipher = XChaCha20Poly1305::new(key.into()); let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng); let ciphertext = cipher.encrypt(&nonce, plaintext)?; // 5. Отправка: nonce || ciphertext || tag

Nonce генерируется случайно (не последовательно), так как в mesh-сети сообщения могут приходить не по порядку. При 192-битном случайном nonce вероятность коллизии пренебрежимо мала даже при миллионах сообщений.

Прямая секретность с помощью Ratchet

Статические ключи — уязвимость: если узел скомпрометирован, все прошлые сообщения раскрыты. GhostWire реализует паттерн Double Ratchet (вдохновлён Signal) для сессионных ключей:

Session Key = HKDF(previous_key, message_number)

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

struct RatchetState { root_key: [u8; 32], chain_key: [u8; 32], message_number: u32, } impl RatchetState { fn next_message_key(&mut self) -> [u8; 32] { let key = hkdf_expand(&self.chain_key, &self.message_number.to_le_bytes()); self.chain_key = hkdf_expand(&self.chain_key, b"chain"); self.message_number += 1; key } fn advance_chain(&mut self, new_chain_key: [u8; 32]) { self.root_key = hkdf_expand(&self.root_key, &new_chain_key); self.chain_key = new_chain_key; } }

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

Постквантовый гибрид

XChaCha20-Poly1305 устойчив к квантовым атакам (не основан на факторизации или дискретном логарифме). Но обмен ключами X25519 — нет: квантовый компьютер сможет вычислить общий секрет.

GhostWire решает это с помощью гибридного обмена ключами:

shared_secret = X25519(my_secret, peer_public) || ML-KEM-768(my_secret, peer_public)

Оба общих секрета (классический и постквантовый) объединяются и подаются на вход HKDF. Злоумышленнику нужно взломать оба, чтобы получить ключ. Если ML-KEM-768 падёт под квантовыми вычислениями, X25519 всё ещё защищает (и наоборот).

fn hybrid_shared_secret( x25519_secret: &EphemeralSecret, x25519_public: &PublicKey, mlkem_ciphertext: &[u8], ) -> [u8; 64] { let classical = x25519_secret.diffie_hellman(x25519_public); let pq = mlkem_decaps(mlkem_secret, mlkem_ciphertext); let mut combined = [0u8; 64]; combined[..32].copy_from_slice(classical.as_bytes()); combined[32..].copy_from_slice(&pq); combined }

Производительность на ограниченных устройствах

Мы провели замеры на ESP32-S3 (240 МГц, без AES-NI):

ОперацияXChaCha20-Poly1305AES-256-GCM
Шифрование 1 КБ12 мкс18 мкс
Шифрование 64 КБ0,8 мс1,1 мс
Вывод ключа45 мкс45 мкс

ChaCha20 оказался примерно на 33% быстрее на этой платформе. На x86 с AES-NI выигрывает AES, но GhostWire не ориентирован на дата-центры.

Что дальше

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

GitHub: github.com/Phantomojo/GhostWire-secure-mesh-communication

Сайт: ghostwire.cc

Создано в Найроби, под эгидой RVC.

По материалам: dev.to. Текст переработан редакцией Слогера.

← На главную

Рекламное место — Конец поста
Реклама · Слогер

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

Войдите, чтобы комментировать.

Пока нет комментариев. Будьте первым.