Сквозное шифрование: протокол Signal

Как переписка остаётся секретной даже от сервера мессенджера — и даже если один ключ утёк.

Сквозное шифрование (E2EE) — модель, при которой сообщение шифруется на устройстве отправителя и расшифровывается только на устройстве получателя; промежуточный сервер видит лишь нечитаемый шифротекст.

Протокол Signal лежит в основе сквозного шифрования Signal, WhatsApp и многих других мессенджеров. Его задача — обеспечить не просто секретность, а две сильные гарантии: forward secrecy (утечка сегодняшнего ключа не раскроет вчерашние сообщения) и post-compromise security (после компрометации система сама «лечится» и снова становится безопасной). Достигается это связкой двух механизмов: X3DH для установления первого секрета и Double Ratchet для постоянного обновления ключей.

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

Понимание E2EE объясняет, что именно гарантирует и чего не гарантирует «защищённый мессенджер». E2EE прячет содержимое от сервера, но не прячет метаданные (кто, когда, как часто) и бессилен, если скомпрометировано само устройство — там сообщения уже расшифрованы. Это знание помогает корректно оценивать риски и не переоценивать защиту.

X3DH: начало диалога

Чтобы зашифровать первое сообщение, нужен общий секрет. Но в мессенджере собеседник может быть офлайн. X3DH (Extended Triple Diffie-Hellman) решает это так: каждый пользователь заранее выкладывает на сервер набор публичных ключей — долговременный ключ личности, подписанный «предключ» и пачку одноразовых предключей. Отправитель забирает эти публичные ключи и выполняет несколько обменов Диффи-Хеллмана сразу, комбинируя их в один общий секрет. Получателю даже не надо быть онлайн — он восстановит тот же секрет, когда зайдёт.

У получателя на сервере лежат публичные:
  IK  — Identity Key      (долговременный)
  SPK — Signed PreKey     (обновляется периодически, подписан IK)
  OPK — One-Time PreKey   (одноразовый)

Отправитель считает несколько DH и смешивает их в общий секрет:
  DH1 = DH(своя IK, чужой SPK)
  DH2 = DH(своя эфемерная, чужой IK)
  DH3 = DH(своя эфемерная, чужой SPK)
  DH4 = DH(своя эфемерная, чужой OPK)
  секрет = KDF(DH1 || DH2 || DH3 || DH4)

Несколько обменов нужны, чтобы связать секрет одновременно с личностью обеих сторон и с одноразовыми значениями — это даёт и аутентификацию, и forward secrecy с самого первого сообщения.

Double Ratchet: ключ на каждое сообщение

Дальше начинается «двойной храповик». Идея: ключ обновляется так часто, что у каждого сообщения он свой, и старые ключи восстановить нельзя. Храповик двойной, потому что крутится по двум осям.

Симметричный храповик

Есть «ключ цепочки». Из него функция KDF выводит ключ конкретного сообщения, а затем продвигает сам ключ цепочки вперёд. Назад процесс необратим: зная сегодняшний ключ цепочки, нельзя вычислить вчерашний. Покажем этот шаг на стандартной библиотеке Python:

import hashlib, hmac

def kdf(chain_key, label):
    return hmac.new(chain_key, label, hashlib.sha256).digest()

ck = b'initial-chain-key-32-bytes-long!'   # ключ цепочки (32 байта)
for i in range(3):
    mk = kdf(ck, b'\x01')   # ключ сообщения
    ck = kdf(ck, b'\x02')   # продвигаем ключ цепочки
    print('сообщение', i, 'ключ:', mk.hex()[:16], '...')

Вывод:

сообщение 0 ключ: 7cb4c4759efe0980 ...
сообщение 1 ключ: db9f6387687cf923 ...
сообщение 2 ключ: 40d302e13a9bb151 ...

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

DH-храповик

Симметричный храповик идёт только вперёд, поэтому если злоумышленник украл текущий ключ цепочки, он сможет читать будущие сообщения. Чтобы «вылечиться», в дело вступает вторая ось: при каждом ответе стороны подмешивают новый эфемерный обмен Диффи-Хеллмана и пересоздают корневой ключ. После такого «поворота» украденного состояния уже недостаточно — это и есть post-compromise security.

Forward secrecy и post-compromise security

Эти два свойства — зеркальные. Forward secrecy смотрит в прошлое: компрометация сейчас не раскрывает старые сообщения, потому что их ключи уже стёрты. Post-compromise security смотрит в будущее: после компрометации, как только пройдёт новый DH-поворот, связь снова становится защищённой. Вместе они означают, что окно ущерба от утечки одного ключа максимально узкое.

Сравним с обычным шифрованием на одном долговременном ключе. Там утечка ключа раскрывает всю переписку — и прошлую, и будущую, — пока ключ не сменят вручную. Double Ratchet же меняет ключи автоматически на каждом сообщении и на каждом ответе, поэтому даже разовый доступ к памяти устройства даёт атакующему в худшем случае узкое окно, а не весь архив. Именно поэтому современные мессенджеры выбирают эту схему, а не «зашифровать всё одним ключом канала».

Сравнение с шифрованием «точка-точка»

Важно не путать E2EE со «шифрованием транспорта», как в TLS. TLS защищает участок «клиент — сервер»: на сервере мессенджера трафик расшифровывается, и теоретически сервер видит содержимое. E2EE же шифрует «отправитель — получатель», и сервер выступает лишь почтальоном, передающим запечатанные конверты. Поэтому претензия «мы используем TLS» не равна «у нас сквозное шифрование» — это разные уровни защиты, и защитнику важно различать их при оценке мессенджера.

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

Состояние сессии — это несколько ключей: корневой ключ, ключ цепочки отправки и ключ цепочки приёма. Корневой ключ обновляется DH-храповиком, ключи цепочек — симметричным. Каждое сообщение несёт в заголовке новый публичный DH-ключ отправителя, чтобы собеседник мог провернуть свой храповик и синхронизироваться. Сообщения, пришедшие не по порядку, обрабатываются за счёт сохранения «пропущенных» ключей до их использования.

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

  • Сверяйте отпечатки ключей. E2EE защищает от сервера, но не от подмены при первом контакте — сравните «код безопасности»/QR с собеседником по другому каналу.
  • Помните про метаданные. Содержимое скрыто, но факт, время и участники переписки — нет; для их защиты нужны отдельные меры.
  • Берегите устройство. На конечной точке сообщения уже расшифрованы — блокировка экрана, шифрование диска и контроль вредоносного ПО критичны.
  • Реагируйте на смену ключей. Уведомление «ключ безопасности изменился» — повод перепроверить, не подменён ли собеседник, а не просто нажать «ок».

Итоги

  • X3DH устанавливает общий секрет даже с офлайн-собеседником, комбинируя несколько обменов Диффи-Хеллмана.
  • Double Ratchet выдаёт уникальный ключ на каждое сообщение: симметричный храповик — forward secrecy, DH-храповик — post-compromise security.
  • E2EE прячет содержимое от сервера, но не метаданные и не данные на скомпрометированном устройстве.
  • Сверка отпечатков ключей — обязательный шаг защиты от подмены при первом контакте.
Проверьте себя
1. В чём разница между forward secrecy и post-compromise security в протоколе Signal?
AЭто два названия одного и того же свойства
BForward secrecy защищает прошлые сообщения от будущей утечки, а post-compromise security восстанавливает безопасность после утечки благодаря новому DH-повороту
CForward secrecy шифрует метаданные, а post-compromise security — содержимое
DForward secrecy работает только в X3DH, а post-compromise — только в TLS
2. Зачем в X3DH нужен набор предключей (SignedPreKey, One-Time PreKey), выложенный на сервер заранее?
AЧтобы сервер мог читать сообщения
BЧтобы установить общий секрет с собеседником, даже когда он офлайн, и обеспечить forward secrecy с первого сообщения
CЧтобы ускорить отправку картинок
DЧтобы заменить сертификаты TLS