Skip to content

Безопасность

Безопасность в csend построена на одной простой идее: кодовая фраза — это единственный секрет, и она никогда не передаётся по сети. Даже если кто-то слушает весь трафик в Wi-Fi сети, он не сможет прочитать ваши файлы.

Что выводится из кодовой фразы

Из одной фразы вроде 3-royal-mount-dance программа вычисляет две криптографические производные и сохраняет саму фразу для отображения:

plantuml Diagram
ЧтоФункцияТипНазначение
Room IDcode_to_room_id()Криптографическая производная (BLAKE3 hash)Проверка фразы при P2P-рукопожатии (Handshake)
Encryption Keycode_to_encryption_key()Криптографическая производная (BLAKE3 KDF)Шифровать и расшифровывать файлы
Сама фразаОригинал (без преобразования)Показать отправителю, чтобы он сказал её получателю

Все три значения не передаются по сети: Room ID используется только в P2P Handshake (внутри Noise-зашифрованного канала), Encryption Key вообще не покидает устройство, а фраза показывается только на экране отправителя.

Генерация кодовой фразы

Фраза генерируется случайно при каждой отправке:

формат: {цифра}-{слово}-{слово}-{слово}
пример: 7-castle-river-bloom
ЧастьДиапазонЗачем
Цифра1–9Быстрая визуальная проверка
Слова256 вариантов каждоеОбеспечивают случайность

Количество комбинаций: 9 × 256 × 256 × 256 = 150 994 944 (больше 150 миллионов). Угадать фразу перебором за разумное время невозможно.

Словарь содержит 256 простых английских слов из разных категорий (животные, цвета, погода...), чтобы их было легко произнести и запомнить: apple, brave, castle, dream...

Хеширование — BLAKE3

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

Room ID

rust
pub fn code_to_room_id(code: &str) -> String {
    // 1. Хешируем фразу → 32 байта
    let hash = blake3::hash(code.as_bytes());
    // 2. Берём первые 16 hex-символов
    hash.to_hex()[..16].to_string()
}

Пример: "3-royal-mount-dance""a3f8b2c1e9d04567"

Room ID не передаётся в Discovery — обнаружение работает без него. Room ID используется только в P2P-рукопожатии (Handshake { code_hash }): получатель отправляет хеш введённой фразы, а отправитель сверяет его со своим. Так оба убеждаются, что знают одну и ту же фразу.

Encryption Key (KDF)

rust
pub fn code_to_encryption_key(code: &str) -> [u8; 32] {
    blake3::derive_key("kontinuum-send-encryption-v1", code.as_bytes())
}

KDF (Key Derivation Function) — специальный режим BLAKE3, который выводит ключ из пароля. Контекстная строка "kontinuum-send-encryption-v1" гарантирует, что даже одинаковые фразы в разных приложениях дадут разные ключи.

Почему два разных вывода? Room ID и Encryption Key используют разные алгоритмы BLAKE3 (hash vs derive_key), поэтому знание Room ID не помогает узнать Encryption Key.

Шифрование файлов — ChaCha20Poly1305

ChaCha20Poly1305 — это AEAD-шифр (Authenticated Encryption with Associated Data). Он одновременно:

  1. Шифрует данные (никто не прочитает без ключа)
  2. Аутентифицирует данные (никто не подменит без ключа)
plantuml Diagram

Nonce — уникальный номер для каждого чанка

Шифр ChaCha20 требует nonce (number used once) — уникальное число для каждого шифрования. Если использовать один и тот же nonce дважды с одним ключом, шифрование ломается.

В csend nonce детерминированный — вычисляется из позиции чанка:

nonce (12 байт) = file_index (4 байта LE) || offset (8 байт LE)
plantuml Diagram

Почему это безопасно?

  • Каждый чанк имеет уникальную пару (file_index, offset)
  • Значит, каждый nonce уникален в рамках одной передачи
  • Ключ тоже уникален (выводится из случайной фразы)

Риск повторного использования фразы: Если одну и ту же кодовую фразу использовать для двух разных передач, ключ и nonce'ы совпадут для чанков с одинаковыми позициями. Это теоретически ослабляет шифрование (XOR двух шифротекстов = XOR двух открытых текстов). На практике это маловероятно, потому что фраза генерируется случайно при каждой отправке и используется один раз.

Размер данных после шифрования

Каждый зашифрованный чанк на 16 байт больше оригинала. Эти 16 байт — аутентификационный тег (Poly1305 MAC). Тег позволяет получателю проверить, что данные не были изменены.

Исходный чанк:   256 КиБ = 262 144 байт
Зашифрованный:   262 144 + 16 = 262 160 байт

Модель угроз

Что произойдёт при разных атаках:

plantuml Diagram
УгрозаЗащита
Перехват трафикаДва уровня: Noise шифрует канал (заголовки, метаданные), ChaCha20Poly1305 шифрует файлы
Подмена данныхPoly1305 аутентификация (тег не совпадёт → ошибка)
Угадывание фразы150+ млн комбинаций, онлайн-проверка медленная (~1 сек на попытку)
Повтор чанка (replay)Детерминированный nonce + идемпотентная запись: повторный чанк перезапишет те же байты
Утечка кодовой фразыНи фраза, ни её хеш не передаются в Discovery
Утечка списка файловИмена и размеры видны в Discovery (трейдофф: удобство vs приватность)

Два уровня шифрования: libp2p Noise шифрует весь транспорт — пассивный наблюдатель не видит даже типы сообщений (Handshake, Chunk и т.д.). Однако Discovery-сообщения (имена файлов, размеры) отправляются всем пирам в сети как часть протокола обнаружения, поэтому любой участник сети с csend их получит. ChaCha20 — второй барьер: даже если Noise будет скомпрометирован, содержимое файлов останется защищённым.

Что НЕ защищено

Честность важна — вот что csend не скрывает:

  1. Имена и размеры файлов — передаются открыто в Discovery и Offer
  2. Имя устройства — видно всем в сети
  3. Факт передачи — наблюдатель знает, что файлы передаются
  4. Размер трафика — по объёму данных можно оценить, сколько файлов передано

Это осознанный компромисс: полная анонимность усложнила бы обнаружение устройств.