Обмен ключами и согласование
Как двое договариваются об общем секрете по открытому каналу, где всё слышит злоумышленник.
Обмен ключами Диффи-Хеллмана — способ для двух сторон вычислить общий секрет, обмениваясь только публичными значениями, так что подслушивающий канал не может этот секрет восстановить.
Эта идея — фундамент, на котором стоят и 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.