Сейф надёжный, ключ под ковриком

Сейф надёжный, ключ под ковриком

Когда мы слышим слово «взлом», воображение рисует одинокого гения, который в тёмной комнате решает невозможное математическое уравнение и вскрывает неприступный шифр. Красивый образ, но почти всегда неправильный.

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

Брюс Шнайер, один из самых известных криптографов в мире, сформулировал это как закон: «Любой может создать шифр, который сам не сможет взломать» (Schneier on Security). Проблема в том, что из этого ничего не следует: то, что вы не можете взломать собственное решение, не значит, что его не может взломать кто-то другой. И чаще всего этот «кто-то другой» атакует не математику, а реализацию.

Алгоритм и реализация — в чём разница

Чтобы понять, где именно возникают уязвимости, нужно разделить два понятия.

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

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

Исследование MIT под руководством Давида Лазара (Lazar et al., 2014) показало, что подавляющее большинство криптографических уязвимостей в реальных системах — это ошибки реализации, а не слабости алгоритмов. Ту же мысль подтверждает Надя Хенингер, чья команда в 2012 году просканировала весь интернет и нашла десятки тысяч реальных серверов с уязвимыми криптографическими ключами. И произошло это не потому что RSA слаб, а потому что генераторы случайных чисел работали неправильно (Mining Your Ps and Qs, USENIX 2012).

Дальше — пять реальных примеров, каждый из которых показывает, как именно это происходит.

Плохая случайность: когда «монетка» падает одной стороной

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

Самый показательный случай произошёл с Debian Linux в 2008 году. Разработчик, исправляя предупреждение статического анализатора, случайно удалил строку кода, которая отвечала за добавление энтропии (непредсказуемости) в генератор случайных чисел OpenSSL. В результате все ключи, сгенерированные на Debian и Ubuntu за два года, опирались на настолько слабую случайность, что пространство возможных ключей сжалось до примерно 32 000 вариантов вместо астрономического числа. Любой из этих ключей можно было подобрать за несколько минут (CVE-2008-0166).

Команда Нади Хенингер пошла ещё дальше. В рамках исследования «Mining Your Ps and Qs» (USENIX Security 2012, лучшая статья конференции) они за 24 часа просканировали весь интернет, собрав публичные ключи с 22 миллионов серверов. Оказалось, что 0,75 % TLS-сертификатов используют повторяющиеся ключи из-за недостаточной энтропии при генерации. Исследователи смогли вычислить закрытые ключи для 0,5 % TLS-серверов и 1,03 % SSH-серверов. Причина была не в слабости RSA, а в «дыре энтропии» при загрузке встроенных устройств — маршрутизаторов, файрволов, VPN-серверов от десятков производителей.

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

Повторный nonce: одноразовый талон, использованный дважды

Nonce (от number used once) — это одноразовое уникальное значение, которое должно использоваться строго один раз. Представьте его как пломбу на упаковке: если использовать её повторно, система «ломается».

В 2010 году группа хакеров fail0verflow показала, что Sony допустила катастрофическую ошибку в защите PlayStation 3. Для цифровых подписей Sony использовала алгоритм ECDSA (Elliptic Curve Digital Signature Algorithm — алгоритм цифровой подписи на эллиптических кривых), который математически надёжен. Но при подписании Sony каждый раз использовала один и тот же nonce вместо случайного. Нарушение единственного условия позволило хакерам вычислить закрытый ключ Sony, после чего они получили возможность подписывать произвольный код для запуска на консоли. Математика была безупречна. Ошибка была ровно в одном значении.

Ещё более масштабный пример — протокол WEP (Wired Equivalent Privacy), который защищал Wi-Fi-сети с 1997 года. WEP использовал шифр RC4 с 24-битным вектором инициализации (initialization vector, IV) — уникальным значением, которое меняется от пакета к пакету. Проблема: 24 бита дают всего 16,7 миллиона возможных значений. На загруженной точке доступа все они исчерпываются примерно за 5 часов, после чего векторы начинают повторяться. Злоумышленник, перехвативший достаточно пакетов с одинаковыми IV, мог вычислить ключ шифрования. В 2001 году исследователи Флюрер, Мантин и Шамир опубликовали атаку, а в 2005 году ФБР публично продемонстрировала взлом WEP-сети за три минуты.

Проблема WEP была не в RC4 как таковом, а в том, как RC4 использовали: слишком маленькое пространство IV, отсутствие защиты от повторений, и линейная контрольная сумма CRC-32 вместо криптографической. Урок: алгоритм математически надёжен только при соблюдении условий использования. Нарушил условие — и математика уже не спасает.

Утечки через время: атака, о которой не подозревали

Программа может нечаянно выдавать секрет не напрямую, а косвенно — через время ответа, потребление памяти, энергопотребление или поведение процессора. Такие уязвимости называют атаками по побочному каналу (side-channel attack — «атака не на саму математику, а на косвенные признаки работы системы»).

Самый доступный для понимания вариант такой атаки — атака по времени (timing attack). В 1996 году криптограф Пол Кохер опубликовал основополагающую работу о том, как различия во времени выполнения криптографических операций позволяют извлекать секретные ключи. Идея проста: если программа сравнивает секретные строки посимвольно и останавливается на первой ошибке, злоумышленник может по времени ответа понять, сколько символов угадано правильно.

В 2003 году Брамли и Бонех продемонстрировали, что атака по времени работает даже через интернет. Они извлекли закрытый ключ RSA из OpenSSL-сервера, измеряя время ответа удалённого сервера. Алгоритм RSA при этом был абсолютно корректен. Уязвимость была в нескольких строках кода, которые выполнялись чуть быстрее или чуть медленнее в зависимости от значения секретного ключа.

Именно поэтому современные криптографические библиотеки стремятся к константному времени выполнения, то есть операции должны выполняться за одинаковое время вне зависимости от значения секрета. Это не ускорение, а намеренное выравнивание: система тратит одно и то же время, даже если могла бы ответить быстрее.

Ключ под ковриком: неправильное хранение секретов

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

В 2024 году на GitHub было обнаружено более 39 миллионов утёкших секретов — API-ключей, паролей, токенов, криптографических сертификатов (GitHub Blog). Исследователи GitGuardian зафиксировали 23,8 миллиона новых жёстко вшитых секретов (hardcoded secrets) только в публичных репозиториях за год — рост на 25 % (GitGuardian). Более 90 % утёкших секретов оставались действительными спустя 5 дней после публикации, а 70 % секретов, обнаруженных в 2022 году, всё ещё были активны в 2024-м.

Жёстко вшитый ключ — ключ, записанный прямо в исходном коде. Разработчик вставляет его «для простоты», забывает убрать, и он попадает в репозиторий. Результат: злоумышленнику не нужно ломать шифр, достаточно прочитать код. По данным IBM, средняя стоимость утечки, связанной с украденными учётными данными, составляет 4,81 миллиона долларов, а на обнаружение и устранение таких инцидентов уходит в среднем 292 дня — дольше, чем при любом другом векторе атаки (IBM Cost of a Data Breach Report 2024).

Две строки, которые сломали всё: Apple «goto fail»

Иногда катастрофа — это буквально одна лишняя строка кода. В феврале 2014 года Apple выпустила экстренное обновление для iOS и macOS. Оказалось, что с октября 2013 года в SSL/TLS-проверке подписей на всех устройствах Apple была уязвимость CVE-2014-1266, получившая название «goto fail».

В функции SSLVerifySignedServerKeyExchange присутствовала дублированная строка goto fail;. Первый goto был привязан к условию if, а второй — нет: он выполнялся всегда, безусловно. Из-за него код перескакивал через проверку подписи сервера. В результате система принимала поддельные сертификаты, позволяя атаку «человек посередине» (man-in-the-middle) на любое HTTPS-соединение.

Наиболее вероятная причина — неудачный копипаст или плохо разрешённый конфликт при слиянии веток кода. Компилятор мог бы предупредить о недостижимом коде, но соответствующий флаг (-Wunreachable-code) не был включён. Модульных тестов для этой функции не было. Алгоритм SSL/TLS был безупречен — ошибка пряталась в паре строк рядом с ним.

Показательный факт: 2014 год стал годом, когда каждая крупная реализация SSL/TLS содержала серьёзную уязвимость — Apple (goto fail), OpenSSL (Heartbleed), GnuTLS (аналогичный goto fail), Microsoft (ImperialViolet).

Опасные интерфейсы: когда библиотека подставляет разработчика

Даже хорошая криптографическая библиотека может быть опасной, если её интерфейс программирования (API, Application Programming Interface — способ, которым программа даёт себя использовать) заставляет разработчика принимать слишком много тонких решений.

Если библиотека предлагает десять вариантов шифрования, пять режимов работы и не предупреждает о рисках, разработчик почти неизбежно где-то ошибётся. Выберет небезопасный режим. Забудет про аутентификацию. Подставит предсказуемый nonce.

Криптограф Дэниел Бернштейн создал библиотеку NaCl (Networking and Cryptography Library), спроектированную по противоположному принципу: самый простой способ использования одновременно и самый безопасный (The Security Impact of a New Cryptographic Library, 2012). Вместо десятков настроек — две функции: crypto_box для асимметричного и crypto_secretbox для симметричного шифрования. Безопасные настройки по умолчанию. Никакого потока данных от секретов к адресам загрузки или условиям ветвления. Никаких padding-oracle уязвимостей. Бернштейн назвал такой подход ошибкоустойчивым дизайном (misuse-resistant design) — «дизайном, который мешает пользователю случайно сделать опасную ошибку».

Наследница NaCl — библиотека libsodium — стала стандартом для проектов, где криптография должна «просто работать». Её философию можно сформулировать так: безопасная криптография — это не только сильный алгоритм, но и такой интерфейс, который трудно использовать неправильно.

Почему так происходит

Математическая модель криптографического алгоритма предполагает идеальный мир: настоящую случайность, правильное хранение секретов, аккуратное выполнение каждого шага, отсутствие утечек через аппаратуру, корректную реализацию.

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

Что делают, чтобы мост через эту пропасть стал прочнее

Современная индустрия безопасности накопила набор практик, которые уменьшают риск:

  • готовые проверенные библиотеки — лучше использовать NaCl, libsodium, BoringSSL (Google), чем писать собственную криптографию. Правило «не изобретай свою криптографию» — одно из самых важных в безопасности;
  • безопасные значения по умолчанию — настройки, которые система выбирает сама, должны быть безопасными, а не быстрыми или совместимыми;
  • минимизация выбора — чем меньше опасных ручных настроек, тем меньше шансов ошибиться;
  • константное время выполнения — криптографические операции выполняются за фиксированное время, чтобы исключить утечки через побочные каналы;
  • аудиты безопасности — независимая проверка кода экспертами. После Heartbleed в 2014 году, когда ошибка в OpenSSL затронула 17 % всех HTTPS-серверов в мире, индустрия стала относиться к аудитам значительно серьёзнее;
  • управление секретами — вместо жёстко вшитых ключей используются специализированные хранилища: HashiCorp Vault, AWS Secrets Manager и их аналоги. GitHub с 2024 года блокирует коммиты, содержащие распознанные секреты, через систему Push Protection.

Вывод

Самая слабая часть защищённой системы — не обязательно шифр. Чаще это человек, интерфейс, настройки или пара неосторожных строк кода. Каждый из примеров в этом посте — Debian OpenSSL, Sony PS3, WEP, Apple goto fail, 39 миллионов утёкших секретов на GitHub — это история о том, как мощная математика была подведена слабой инженерией.

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

Сейф может быть идеальным. Но если ключ лежит под ковриком, проблема не в сейфе.

Источники и дополнительное чтение

Предыдущий
Наверх