Обмен ключами и согласование

Как двое договариваются об общем секрете по открытому каналу, где всё слышит злоумышленник.

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

Эта идея — фундамент, на котором стоят и TLS, и Signal, и многое другое. Симметричное шифрование требует, чтобы обе стороны уже знали общий ключ, — но как его согласовать через интернет, где любой может читать трафик? Диффи-Хеллман (1976) дал контринтуитивный ответ: можно публично обменяться данными так, что в итоге у обоих окажется одинаковый секрет, а у наблюдателя — нет.

Зачем это знать защитнику

Обмен ключами — точка, где рождается вся последующая защита сессии. Если он скомпрометирован, бесполезно любое сильное шифрование поверх. Понимание DH объясняет, откуда берётся forward secrecy, почему эфемерные ключи лучше статических и где именно подкрадывается человек посередине. Это базовый навык для оценки безопасности любого защищённого канала.

Диффи-Хеллман: как это работает

Стороны заранее договариваются о публичных параметрах: большом простом числе p и генераторе g. Каждый выбирает свой приватный показатель и вычисляет публичное значение возведением g в эту степень по модулю p. Затем обмениваются публичными значениями и возводят полученное от партнёра в свою приватную степень. За счёт свойств степеней результат совпадает у обоих.

p = 23   # общий модуль (в жизни — сотни цифр)
g = 5    # общий генератор

a = 6           # приватный показатель Алисы
b = 15          # приватный показатель Боба
A = pow(g, a, p)   # публичное значение Алисы
B = pow(g, b, p)   # публичное значение Боба

# обмениваются A и B по открытому каналу, затем:
secret_alice = pow(B, a, p)
secret_bob   = pow(A, b, p)
print('публичное A:', A)
print('публичное B:', B)
print('секрет Алисы:', secret_alice)
print('секрет Боба :', secret_bob)
print('совпало:', secret_alice == secret_bob)

Вывод:

публичное A: 8
публичное B: 19
секрет Алисы: 2
секрет Боба : 2
совпало: True

Подслушивающий знает p, g, A и B, но чтобы найти секрет, ему нужно вычислить приватный показатель из публичного значения — это задача дискретного логарифма, которая при больших p практически неразрешима.

ECDH: тот же принцип на эллиптических кривых

Классический DH требует очень больших чисел (2048+ бит), чтобы быть стойким. ECDH (Elliptic Curve Diffie-Hellman) делает то же самое на математике эллиптических кривых, где аналог дискретного логарифма ещё сложнее. Это даёт ту же стойкость при гораздо меньших ключах: кривая на 256 бит примерно эквивалентна классическому DH на 3072 бита. Меньше байт — быстрее вычисления и меньше трафика, поэтому современные протоколы предпочитают именно ECDH (например, кривую x25519 в TLS 1.3 и Signal). Принцип неизменен: приватный скаляр, публичная точка, общий секрет из их комбинации.

Защита от MITM

У «голого» Диффи-Хеллмана есть фатальная брешь: он защищает от пассивного подслушивания, но не от активного посредника. Атака «человек посередине» (MITM) выглядит так:

Алиса ⇄ [ Меллори ] ⇄ Боб

Меллори ведёт ДВА обмена сразу:
  - с Алисой как будто она Боб
  - с Бобом как будто он Алиса
Получаются два разных секрета, оба известны Меллори.
Она расшифровывает, читает/меняет и перешифровывает — стороны
даже не подозревают, что общаются через посредника.

Лекарство — аутентификация публичных значений. Стороны должны убедиться, что обменялись ключами именно друг с другом, а не с самозванцем. Способы разные: подпись DH-значений сертификатом (так делает TLS), сверка отпечатков ключей по другому каналу (так делают мессенджеры), или заранее общий пароль. Без аутентификации обмен ключами не имеет смысла против активного атакующего — это ключевой урок.

Согласование сессионных ключей и роль KDF

Сырой результат DH использовать напрямую как ключ нельзя: у него неудобная структура и обычно нужен не один ключ, а несколько (для шифрования в каждую сторону, для контроля целостности). Поэтому общий секрет пропускают через KDF (Key Derivation Function, функцию формирования ключей). KDF берёт «сырой» секрет, опциональную «соль» и контекст-метку и выдаёт ключи нужной длины с равномерной структурой. Стандарт здесь — HKDF, и его «выжимающий» шаг — это, по сути, HMAC. Покажем учебную деривацию на стандартной библиотеке Python:

import hashlib, hmac

shared = bytes.fromhex('00112233445566778899aabbccddeeff')  # общий секрет DH
salt   = b'handshake salt'
info   = b'session traffic key'

# HKDF-Extract: из сырого секрета получаем равномерный PRK
prk = hmac.new(salt, shared, hashlib.sha256).digest()
# HKDF-Expand (один блок): растягиваем PRK в ключ нужной длины
okm = hmac.new(prk, info + b'\x01', hashlib.sha256).digest()
key = okm[:16]   # 128-битный сессионный ключ
print('PRK   :', prk.hex())
print('ключ16:', key.hex())

Вывод:

PRK   : 4e247e6fea4f7ecfdfba169cecd9ac68124438ceb81c15f4bb2253ee4e66e601
ключ16: 1173f0d1077024cbf786061e35167453

Меняя метку info, из одного и того же секрета получают разные независимые ключи — именно так протоколы выводят отдельные ключи для отправки и приёма.

Как это работает под капотом

Стойкость DH опирается на одностороннюю «трудность»: возвести в степень по модулю легко, а извлечь показатель (дискретный логарифм) — вычислительно крайне дорого. У ECDH аналогичную роль играет сложность «деления» точки на кривой. Эфемерные ключи (новая пара на каждую сессию) превращают этот обмен в источник forward secrecy: записанный сегодня трафик не раскроется завтра, потому что приватные показатели той сессии уже уничтожены. На горизонте — квантовые компьютеры, способные сломать и DH, и ECDH; поэтому индустрия движется к постквантовым и гибридным схемам обмена ключами.

Как защититься

  • Всегда аутентифицируйте обмен. Голый DH без проверки личности — открытая дверь для MITM; подпись, сертификат или сверка отпечатков обязательны.
  • Используйте эфемерные ключи. Свежая пара на сессию даёт forward secrecy; статические DH-ключи такой защиты лишают.
  • Берите проверенные кривые и параметры. Не изобретайте свои группы; используйте стандартные (x25519, secp256r1) и достаточную длину для классического DH (2048+ бит).
  • Прогоняйте секрет через KDF. Не применяйте сырой результат DH как ключ — выводите ключи через HKDF с разными метками для разных целей.
  • Не пишите криптографию сами. Используйте проверенные библиотеки; ошибки реализации обмена ключами тихи и опасны.

Итоги

  • Диффи-Хеллман позволяет согласовать общий секрет по открытому каналу: приватные показатели не передаются, а наблюдатель упирается в задачу дискретного логарифма.
  • ECDH даёт ту же стойкость на эллиптических кривых при меньших ключах, поэтому используется в современных протоколах.
  • Без аутентификации публичных значений обмен уязвим к человеку посередине — подпись или сверка отпечатков обязательны.
  • Сырой секрет всегда пропускают через KDF (HKDF), чтобы получить качественные сессионные ключи; эфемерные ключи добавляют forward secrecy.
Проверьте себя
1. От какой угрозы алгоритм Диффи-Хеллмана НЕ защищает сам по себе, без дополнительных мер?
AОт пассивного подслушивания канала
BОт активного человека посередине (MITM), который ведёт два обмена сразу
CОт утечки общего секрета из-за дискретного логарифма
DОт повтора пакетов
2. Зачем результат обмена Диффи-Хеллмана пропускают через KDF (например, HKDF), а не используют напрямую?
AЧтобы сделать секрет короче для экономии трафика
BЧтобы получить равномерные ключи нужной длины и вывести несколько независимых ключей из одного секрета
CЧтобы зашифровать секрет публичным ключом
DKDF не нужен — сырой секрет можно использовать как ключ
3. Почему ECDH предпочитают классическому Диффи-Хеллману в современных протоколах?
AECDH не требует аутентификации
BECDH даёт сравнимую стойкость при значительно меньшем размере ключей, что быстрее и экономнее
CECDH невозможно сломать даже квантовым компьютером
DECDH работает без приватных ключей