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

Управление ключами и злоупотребление криптографией

В продакшене почти никогда не ломаются алгоритмы — ломается управление ключами. Где живут ключи, как они ротируются и горстка паттернов злоупотреблений (захардкоженные ключи, ECB, самодельная крипта) дают куда больше пробоев, чем слабые шифры.

SECF Senior ◷ 18 min
Уровень
ОсновыJuniorMiddleSenior

Исследователь безопасности запускает git log -p на свежевыложенном в open-source репозитории и грепает по AES. Тремя коммитами назад, в удалённом файле, лежит const MASTER_KEY = "j8Hn2..."; — тот самый AES-256 ключ, что оборачивает данные каждого клиента. Шифр безупречен: AES-256-GCM, золотой стандарт, невзломанный. Это неважно. Ключ жил в репозитории, репозиторий опубликовали, а git никогда не забывает удалённый файл. Команда «проротировала» алгоритм до сильнее-некуда — и всё равно потеряла всё, потому что секрет, защищающий секрет, лежал открытым текстом в системе контроля версий. Крипта почти никогда не падает на математике. Она падает на ключе.

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

Реальная поверхность атаки — это ключ, а не шифр

Инженеры тратят свою крипто-тревогу не на то. Они спорят AES против ChaCha20, мучаются над выбором кривой — а потом коммитят ключ в GitHub. Современные шифры — AES-256-GCM, ChaCha20-Poly1305 — не слабое звено; ни один продакшен-пробой за последнее десятилетие не сводится к тому, что кто-то взломал AES криптоанализом. Слабое звено — это всё, что вокруг шифра: где хранится ключ, кто может его прочитать, как он ротируется и не залогировал ли его случайно джуниор. Думайте о шифре как о двери банковского хранилища, рассчитанной на тысячу лет сверления, — а об управлении ключами как о том, не оставили ли вы ключ под ковриком. Атакующие не сверлят; они проверяют коврик.

Это переосмысляет всю задачу. Вопрос никогда не «достаточно ли силён AES?». Он звучит так: «может ли атакующий, полностью владеющий выводом шифра, всё равно не продвинуться ни на шаг без ключа, и действительно ли ключ труднодоступен?» Безопасность ключа — это минимум по каждому месту, которого он касается: сильнейший шифр с ключом в переменной окружения, утекающей через стектрейс, страницу ошибки или printenv в скомпрометированном поде, ровно настолько силён, насколько силна эта утечка. Вы защищаете ключ, а шифр — это просто та часть, что уже решена.

Где живут ключи — и проблема «секрета-ноль»

Секрет защищён ровно настолько, насколько защищено его самое слабое место хранения, поэтому «где живёт ключ?» — первый вопрос, и ответы образуют лестницу доверия. В самом низу: захардкожен в исходниках — никогда не приемлемо, потому что исходники копируют, форкают, логируют и (как показывает Hook) иногда выкладывают в open-source со всей историей. Ступенью выше: переменные окружения и конфиг-файлы — лучше исходников, но они утекают через крэш-дампы, дочерние процессы, debug-эндпоинты и /proc. Выше: выделенный менеджер секретов (Vault, AWS Secrets Manager), выдающий короткоживущие, контролируемые по доступу, аудируемые секреты по аутентифицированному каналу. На вершине: KMS или HSM, где материал ключа никогда не покидает границу — вы отправляете внутрь открытый текст и получаете шифртекст, но извлечь сам ключ не можете никогда.

Это последнее свойство и есть весь смысл KMS. Оно схлопывает проблему «секрета-ноль»: каждому секрету нужен другой секрет для защиты, и эта цепочка где-то должна заканчиваться. Если ваш ключ приложения зашифрован мастер-ключом, что защищает мастер-ключ? KMS завершает цепочку в железе, чей корневой ключ сгенерирован внутри устройства и физически неэкспортируем, — так что даже полная компрометация приложения даёт шифртекст и возможность попросить KMS расшифровать (что логируется и отзываемо), но никогда сырой ключ, чтобы уйти с ним. Зрелый паттерн — конвертное шифрование (envelope encryption): сгенерировать свежий случайный ключ данных на каждый объект, зашифровать данные им локально (быстро, без KMS-роундтрипа на каждый байт), затем зашифровать этот маленький ключ данных мастер-ключом KMS и сохранить обёрнутый ключ рядом с шифртекстом. Вы получаете аппаратно-укоренённую защиту ключа и скорость на массиве данных одновременно.

Ротация — это проектное требование, а не рутина

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

Механизм, делающий это переживаемым, — идентификатор ключа (key ID), хранимый рядом с каждым шифртекстом. Шифруем текущим ключом и помечаем вывод его key ID; расшифровываем, читая key ID и доставая именно тот ключ. Теперь можно ввести новый ключ, шифровать новые данные под ним и при этом расшифровывать старые — ротация становится скользящей миграцией вместо «дня X». Классический провал — команда, «проротировавшая» ключ подписи JWT подменой значения, мгновенно инвалидировав каждую живую сессию и выкинув саму себя из собственной админ-панели посреди инцидента, потому что не было заголовка kid и связки ключей, к которой можно откатиться.

Место храненияПути утечкиКлюч извлекаем?Вердикт
Захардкожен в исходникахИстория git, форки, логи, open-sourceВсегда (это открытый текст)Никогда не приемлемо
Env var / конфиг-файлКрэш-дампы, /proc, дочерние процессы, debug-эндпоинтыДа, при компрометации процессаТерпимо, но течёт
Менеджер секретов (Vault, ASM)Кража токена, слишком широкая политика IAMДа, но короткоживуще + аудитХорошо
KMS / HSMЗлоупотребление вызовом расшифровки (логируется, отзываемо)Нет — не покидает границуЛучше всего

Три паттерна злоупотреблений, которые реально кусают

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

Второй, режим ECB. AES — блочный шифр; ECB («electronic codebook») шифрует каждый 16-байтовый блок независимо без сцепления, поэтому одинаковые блоки открытого текста дают одинаковые блоки шифртекста. Хрестоматийное доказательство — «пингвин ECB»: зашифруйте битмап Tux в ECB, и пингвина по-прежнему видно в шифртексте, потому что паттерны выживают. ECB утекает структуру, позволяет перестановку блоков и replay и никогда не является правильным выбором — и при этом он часто дефолт в старых библиотеках и то значение, что джуниор копирует из ответа на Stack Overflow от 2011 года. Используйте аутентифицированный режим (AES-GCM), привязывающий уникальный nonce к каждому сообщению и заодно детектирующий подмену.

Третий, самодельная крипта (roll-your-own) — написание собственного шифра, домашнего «шифрования», на деле являющегося XOR-с-фиксированным-ключом, или ручная реализация известного алгоритма. Причина провала не в том, что инженеры глупы; в том, что крипта ломается на деталях, невидимых для функционального тестирования: сравнение за непостоянное время утекает ключ через тайминг, повторно использованный nonce в GCM катастрофически уничтожает конфиденциальность и аутентификацию, предсказуемый IV открывает атаки с выбранным открытым текстом. Ничего из этого не проявляется как падающий тест — код «работает», он шифрует и расшифровывает — что ровно поэтому так опасно. Правило абсолютно: используйте проверенные, прошедшие peer-review библиотеки (libsodium, аудированный крипто-модуль вашей платформы) и никогда не изобретайте.

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

Почему повторное использование nonce в AES-GCM настолько хуже, чем в старой схеме CBC? GCM построен на похожем на одноразовый блокнот потоке ключей, XOR-ящемся с вашим открытым текстом, плюс тег аутентификации, выведенный из секретного значения. Повторите nonce на двух сообщениях — и атакующий, XOR-нув два шифртекста, полностью сокращает поток ключей, восстанавливая XOR открытых текстов; и, хуже, может восстановить подключ аутентификации GCM, что позволяет ему подделывать валидные сообщения, а не только их читать. Это разница между утечкой данных и передачей ручки для подписи. Вот почему управление nonce — а не выбор шифра — то место, где развёртывания AES-GCM реально умирают.

Крипто-гибкость: считайте, что сегодняшний выбор истечёт

Каждый алгоритм, который вы выбираете сегодня, на таймере. MD5 и SHA-1 когда-то рекомендовались, а теперь сломаны; RSA-1024 устарел; а постквантовая миграция вынуждает индустрию к поголовной смене асимметричных примитивов. Крипто-гибкость (crypto-agility) — это проектирование так, чтобы замена алгоритма была задачей конфигурации и миграции, а не переписывания. Та же дисциплина key ID, что включает ротацию, включает и гибкость: помечайте каждый шифртекст и токен идентификатором алгоритма, чтобы система могла расшифровывать старые данные старой схемой, шифруя новые данные новой. TLS 1.3 (RFC 8446) — канонический пример, сделанный правильно: он согласует cipher suite на каждое соединение и намеренно убрал сломанные опции (статический обмен ключами RSA, RC4, MAC в режиме CBC, SHA-1), а не понёс их дальше, что и есть гибкость, использованная как форсирующая функция вывода слабых вариантов из обращения.

Анти-паттерн — хардкодить алгоритм повсюду — hashlib.md5(...), рассыпанный по всей кодовой базе, — так что в день, когда MD5 объявят мёртвым, вас ждёт многомесячный археологический проект вместо смены конфига. Зрелый ход — спрятать выбор алгоритма за один шов (единый модуль хеширования/шифрования, читающий свою схему из конфига и встраивающий ID схемы в свой вывод), чтобы итоговая, неизбежная миграция была ограниченной.

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

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

Викторина

Команда «проротировала» ключ AES, заменив старое значение ключа новым в менеджере секретов. Существующие зашифрованные записи теперь не расшифровываются. В чём настоящая проектная ошибка?

Викторина

Почему использование режима ECB для AES — это злоупотребление, хотя сам AES-256 не взломан?

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

Упорядочь хранение ключа от наименее к наиболее безопасному (самое слабое сверху):

  1. 1 Захардкожен в исходниках (открытым текстом в истории git навсегда)
  2. 2 Переменная окружения / конфиг-файл (утекает через крэш-дампы, /proc)
  3. 3 Менеджер секретов (короткоживуще, контроль доступа, аудит)
  4. 4 KMS / HSM (ключ никогда не покидает аппаратную границу)
Вспомните перед уходом
  1. 01
    Объясни, почему безопасность зашифрованной системы — это управление ключами, а не шифр, и что даёт KMS/конвертное шифрование по сравнению с переменной окружения.
  2. 02
    Каковы три паттерна злоупотребления криптой и почему крипто-гибкость плюс key ID важны для ротации и миграции алгоритмов?
Итог

Весь урок схлопывается к одному переосмыслению: крипта падает на ключе, а не на шифре. AES-256 и ChaCha20 не взломаны, так что ваша задача — защитить ключ, а ключ силён ровно настолько, насколько надёжно самое слабое место, где он живёт. Это значит держать его вне исходников (захардкоженные ключи живут вечно в истории git), предпочитать KMS или HSM, где мастер-ключ никогда не покидает аппаратную границу, и применять конвертное шифрование, чтобы массив данных шифровался быстро, а каждый ключ данных оборачивался этим защищённым мастер-ключом. Ротация — проектное требование, а не рутина: храните key ID рядом с каждым шифртекстом, чтобы держать несколько ключей сразу и ротировать как скользящую миграцию вместо убивающего сессии «дня X». Избегайте трёх паттернов злоупотреблений — захардкоженных ключей, утекающего структуру детерминизма ECB и самодельной крипты, чьи изъяны невидимы функциональным тестам. Наконец, проектируйте под крипто-гибкость, помечая каждый вывод ID алгоритма, потому что каждый примитив, выбранный сегодня, на таймере. В следующий раз, ревьюя код шифрования, ваш первый вопрос не «силён ли шифр?» — а «где живёт ключ и как это ротируется?».

Практика

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

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

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

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

Примени это

Примени этот урок в реальном проекте.

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

Trademarks belong to their respective owners. Editorial reference only.