open atlas
↑ К треку
Основы безопасности SECF · 02 · 02

Symmetric encryption and AEAD

Сырые режимы блочного шифра утекают структуру и ничего не говорят о подмене. AEAD (AES-GCM, ChaCha20-Poly1305) даёт конфиденциальность и целостность одним примитивом — но один повторённый nonce утекает гаммирующий поток и позволяет подделывать сообщения.

SECF Middle ◷ 15 min
Уровень
ОсновыJuniorMiddleSenior

Скриншот 2008 года годами кочевал по сети: пингвин Linux, зашифрованный, но по-прежнему узнаваемо пингвин. Картинку прогнали через AES — шифр, который никто не взломал, — в режиме ECB, который шифрует каждый 16-байтовый блок независимо. Одинаковые блоки открытого текста давали одинаковые блоки шифртекста, поэтому каждая ровная область одного цвета отображалась в один и тот же шифртекст, и силуэт пережил шифрование целым. Ключ никогда не был проблемой. Режим утёк структуру данных, при этом честно скрыв байты. Годы спустя та же ошибка появляется в костюме: сервис шифрует сессионные токены через AES-CBC, отдаёт их клиенту, а атакующий, который умеет переворачивать байты шифртекста — без ключа, без открытого текста, — тихо переписывает расшифрованный токен, потому что CBC ничего не говорит о том, был ли шифртекст подменён. Тот же урок, выше ставки: сильный шифр в неверной конструкции — это уязвимость с хорошим пиаром.

К концу этого урока ты поймёшь, почему сырые режимы шифра утекают данные или принимают подмену, как AEAD сворачивает конфиденциальность и целостность в один примитив и почему повторённый nonce — это та самая ошибка, что хоронит карьеры.

Симметричные шифры: один ключ, два способа применить его неверно

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

Блочный шифр вроде AES сам по себе — не способ зашифровать сообщение. AES преобразует ровно один блок фиксированного размера — 16 байт — под ключом. Реальные сообщения длиннее 16 байт, поэтому нужен режим работы, который говорит, как применять блочный шифр к множеству блоков. Безопасность живёт или умирает именно в режиме, и две классические неудачи — это две половины зачина:

  • ECB (Electronic Codebook) шифрует каждый блок независимо, без сцепления. Поскольку преобразование детерминировано, один и тот же блок открытого текста всегда даёт один и тот же блок шифртекста — это и есть пингвин. ECB утекает равенство блоков, что утекает структуру, что для многих реальных данных утекает и сами данные. Никогда не используй ECB. По сути нет ситуации, где он — правильный ответ.
  • CBC (Cipher Block Chaining) убирает детерминизм, складывая по XOR каждый блок открытого текста с предыдущим блоком шифртекста (и случайным IV для первого), так что одинаковый открытый текст больше не даёт одинаковый шифртекст. Но CBC обеспечивает только конфиденциальность. Он не говорит тебе, был ли шифртекст изменён в пути. Атакующий, перевернувший бит в шифртексте, получает предсказуемое, управляемое им изменение в расшифрованном открытом тексте — основа атак с переворотом битов и padding-oracle. CBC хранит секрет; он не хранит правду.

Именно второй пробел — шифрование без целостности — инженеры недооценивают, потому что сообщение всё равно расшифровывается во что-то, и демо по-прежнему работает.

Почему конфиденциальность без целостности — это ловушка

Шифрование скрывает содержимое. Само по себе оно не доказывает, что содержимое не изменено или пришло от того, от кого ты думаешь. Считать «это зашифровано» равным «это безопасно» — самая частая криптографическая ошибка в прикладном коде. Классическая демонстрация — padding oracle: при CBC, если сервер раскрывает — хотя бы через разницу во времени или общий 500 против 403, — корректно ли расшифровка дала набивку (padding), атакующий может расшифровать весь шифртекст байт за байтом без ключа, отправляя подобранные шифртексты и наблюдая за оракулом. Никакой ключ не восстанавливается; атакующий просто использует собственное поведение сервера «расшифруй-и-проверь» как побочный канал.

Зрелый рефлекс — это правильно сделанный encrypt-then-MAC: никогда не передавай расшифрованное сообщение в логику приложения, пока не проверил ключевым тегом аутентификации, что шифртекст — ровно то, что произвёл легитимный отправитель. Собирать это самому — выбрать MAC, расположить в правильном порядке, сравнить теги за постоянное время — минное поле (один только порядок encrypt-then-MAC против MAC-then-encrypt потопил реальные протоколы). Ровно поэтому отрасль перестала собирать эти детали вручную и стандартизировала комбинированный примитив.

AEAD: конфиденциальность и целостность в одном примитиве

AEAD — Authenticated Encryption with Associated Data (аутентифицированное шифрование с присоединёнными данными) — современный дефолт, и именно его требует TLS 1.3: спецификация убрала каждый не-AEAD шифр, так что весь трафик TLS 1.3 — это аутентифицированное шифрование. Примитив AEAD делает три работы за один вызов:

  1. Шифрует открытый текст (конфиденциальность).
  2. Производит тег аутентификации над шифртекстом (целостность + аутентичность): при расшифровке, если шифртекст или тег изменили хотя бы на один бит, расшифровка падает громко и не возвращает открытого текста вовсе. Padding-оракулу нечем питаться.
  3. Опционально аутентифицирует присоединённые данные (тот самый «AD») — поля, которые ты хочешь привязать к шифртексту, но передать открыто: заголовок сообщения, порядковый номер, id получателя. AD не шифруется, но покрывается тегом, так что атакующий не может перенести валидный шифртекст под другой заголовок или повторить его под другим порядковым номером.

Доминируют две конструкции AEAD. AES-GCM соединяет AES в режиме счётчика с аутентификатором GHASH; это быстрый выбор на любом CPU с AES-NI, то есть на большинстве серверов. ChaCha20-Poly1305 — это потоковый шифр плюс MAC Poly1305; у него нет аппаратной зависимости, поэтому он работает за постоянное время и быстрее программно — вот почему это дефолт на мобильных и на железе без ускорения AES. Оба дают одну и ту же гарантию. Оба разделяют одно смертельное предусловие.

Грабли повторного nonce

Каждый вызов AEAD берёт nonce (number used once — число, используемое один раз) рядом с ключом. Контракт зашит в названии: для данного ключа nonce никогда не должен повторяться. Соблюдай его — и AEAD превосходен. Нарушь — и гарантии не деградируют плавно, они рушатся.

Для AES-GCM крах тотален и хорошо задокументирован. Под капотом GCM — режим счётчика: nonce засевает гаммирующий поток (keystream). Повтори пару (ключ, nonce) на двух разных сообщениях — и ты зашифровал два открытых текста одной и той же гаммой; сложи по XOR два шифртекста, и гамма сократится, утекая XOR двух открытых текстов — а оттуда часто и сами открытые тексты. Хуже того, повтор nonce в GCM утекает подключ аутентификации (H), что позволяет атакующему подделывать валидные теги для произвольных сообщений под этим ключом. Конфиденциальность и целостность падают от одной ошибки. Это не теория: именно так ломали реальные системы, и именно поэтому 96-битный (12-байтовый) nonce GCM опасен при случайной генерации на масштабе — парадокс дней рождения означает, что коллизии становятся вероятны задолго до 2⁹⁶ сообщений (опасная зона начинается около 2³² сообщений при случайных 96-битных nonce под одним ключом).

Почему это работает

Почему 96-битный случайный nonce «рискован на масштабе», если 2⁹⁶ — астрономически большое число? Потому что важна математика парадокса дней рождения, а не весь объём пространства. Со случайными nonce вероятность коллизии растёт как квадрат числа сообщений: ты пересекаешь значимую вероятность коллизии около 2⁴⁸ сообщений, и NIST ограничивает один ключ 2³² вызовами со случайными 96-битными nonce ровно по этой причине. Два надёжных выхода: используй детерминированный nonce-счётчик (счётчик сообщений на ключ, который доказуемо не повторится, пока не переполнится), либо перейди на конструкцию уровня XChaCha20-Poly1305, чей 192-битный nonce достаточно велик, чтобы случайная генерация была безопасна. Сбой не в слабом шифре — в сильном шифре, накормленном nonce, который его конструкция никогда не обещала терпеть.

ChaCha20-Poly1305 не прощает больше — повтор nonce равно утекает гамму и ключ аутентификации Poly1305, — вот почему практическое правило не зависит от режима: управляй nonce так же тщательно, как ключами. Предпочитай детерминированный счётчик, который ты контролируешь, вместо random(), на уникальность которого ты надеешься, никогда не переиспользуй пару (ключ, nonce) на двух сообщениях и ротируй ключ, прежде чем пространство nonce станет некомфортным. Когда уникальность действительно нельзя гарантировать (stateless-воркеры, нет общего счётчика), берись за устойчивый к злоупотреблению AEAD (AES-GCM-SIV) или конструкцию с большим nonce (XChaCha20-Poly1305), которая переживает случайный повтор вместо того, чтобы рассыпаться.

КонструкцияКонфиденциальностьЦелостностьКогда брать
AES-ECBСломана (утекает равенство блоков)НетНикогда — это пингвин
AES-CBC (без MAC)Да (со случайным IV)Нет → padding oracle, bit-flipТолько legacy-совместимость; не для нового кода
AES-GCMДаДа (тег) — обнуляется при повторе nonceДефолт при AES-NI + уникальных nonce
ChaCha20-Poly1305ДаДа (тег) — обнуляется при повторе nonceНет AES-NI; мобильные; скорость в ПО
AES-GCM-SIVДаДа — переживает случайный повторНельзя гарантировать уникальность nonce

Как выбирает зрелый инженер

Дерево решений короткое. По умолчанию берись за AEAD — никогда сырой ECB или неаутентифицированный CBC. Выбирай AES-GCM на серверах с AES-NI; выбирай ChaCha20-Poly1305 там, где нет аппаратного AES (мобильные, embedded) или нужно постоянное время в ПО по конструкции. Затем трать настоящее внимание на nonce, потому что именно там AEAD реально ломается в продакшене: детерминированный счётчик на ключ бьёт случайную генерацию, а если нельзя гарантировать уникальность по всем процессам, разделяющим ключ, выбирай устойчивый к злоупотреблению или большой-nonce вариант. И не пиши своё — используй проверенную библиотеку платформы (libsodium, Web Crypto AES-GCM, штатный AEAD твоего языка), которая делает сравнение тегов за постоянное время и собирает обвязку правильно.

Выбери лучший вариант

Сервис шифрует сессионные токены через AES-CBC и отдаёт их клиенту. Атакующий, который не может прочитать ключ, переворачивает байты в шифртексте и обнаруживает, что расшифрованный токен меняется в его пользу. Выбери правильный фикс.

Викторина

Почему зашифрованный AES пингвин Linux остаётся узнаваемым в режиме ECB, хотя сам AES не взломан?

Викторина

Ты переиспользуешь одну и ту же пару (ключ, nonce) для двух разных сообщений под AES-GCM. Каково худшее последствие?

Расставь шаги по порядку

Упорядочь эти конструкции от самой сломанной к самому безопасному дефолту для нового кода:

  1. 1 AES-ECB (утекает равенство блоков — пингвин)
  2. 2 AES-CBC без MAC (только конфиденциальность → padding oracle)
  3. 3 AES-GCM с уникальными nonce (AEAD: конфиденциальность + целостность)
  4. 4 AES-GCM-SIV (AEAD, переживающий случайный повтор nonce)
Вспомните перед уходом
  1. 01
    Объясни, почему сырые ECB и сырой CBC оба небезопасны, и как AEAD чинит исходную проблему.
  2. 02
    В чём грабли повторного nonce в AES-GCM, почему это катастрофично и как этого избежать?
Итог

Симметричное шифрование использует один общий ключ в обе стороны и достаточно быстро (AES-NI), что TLS применяет асимметричную криптографию лишь чтобы договориться о симметричном ключе, а потом шифрует основной трафик симметрично. Блочный шифр вроде AES преобразует лишь один 16-байтовый блок, поэтому режим работы решает, как шифр применяется ко всему сообщению — и именно там он ломается. ECB шифрует блоки независимо и детерминированно, утекая структуру (зашифрованный пингвин). CBC сцепляет блоки, чтобы это починить, но даёт конфиденциальность без целостности, что открывает атаки bit-flipping и padding-oracle: шифрование, которое скрывает содержимое, но не доказывает, что оно не подменено, — ловушка, потому что сообщение всё равно расшифровывается во что-то. AEAD — аутентифицированное шифрование с присоединёнными данными — сворачивает обе работы в один примитив: шифрует, производит тег аутентификации над шифртекстом, так что любая подмена проваливает расшифровку без возврата открытого текста, и может привязать открытые присоединённые данные (заголовки, порядковые номера) к шифртексту. AES-GCM — быстрый выбор при AES-NI; ChaCha20-Poly1305 побеждает без аппаратного AES; TLS 1.3 требует AEAD. Оба разделяют одно смертельное предусловие: nonce никогда не должен повторяться под ключом. Повтор утекает гамму (XOR шифртекстов) и, в GCM, подключ аутентификации — так что атакующий восстанавливает открытый текст и подделывает теги из одного повторённого nonce, а риск обостряют 96-битный nonce GCM и парадокс дней рождения. Управляй nonce так же тщательно, как ключами: предпочитай детерминированный счётчик, берись за AES-GCM-SIV или XChaCha20-Poly1305, когда уникальность не гарантировать, и никогда не пиши своё. Поэтому в следующий раз, увидев «это зашифровано», твой первый вопрос: зашифровано каким режимом — и аутентифицировано ли оно?

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 6 завершено
Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.