Чтение онлайн

ЖАНРЫ

19 смертных грехов, угрожающих безопасности программ

Виега Джон

Шрифт:

Тестирование

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

Самый известный набор тестов предложен в стандарте генератора случайных чисел FIPS 140–1 (Federal Information Processing Standard – федеральный стандарт по обработке информации). Один из тестов работает непрерывно, а остальные предполагается запускать в момент инициализации генератора. Обычно гораздо проще включить их прямо в код генератора, чем применять как–то иначе.

Примечание. Тесты типа FIPS абсолютно бесполезны для

данных, вырабатываемых CRNG–генератором. Они имеют смысл лишь для проверки качества истинно случайных чисел. Данные же, получаемые на выходе CRNG, обязаны проходить все статистические тесты с очень высокой вероятностью, даже если числа стопроцентно предсказуемы.

В отдельных случаях, когда вы хотите убедиться, что в некоторой части программы действительно используются случайные данные, имеет смысл посмотреть на несколько последовательных значений. Если они более–менее равномерно распределены по большому пространству (64 и более битов), то беспокоиться, наверное, не о чем. В противном случае нужно изучить реализацию. В любом случае увеличение значения на 1 – это очевидная проблема.

Примеры из реальной жизни

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

Браузер Netscape

В 1996 году студенты–старшекурсники Ян Голдберг и Дэвид Вагнер обнаружили, что в реализации SSL в браузере Netscape «случайные» сеансовые ключи создаются путем применения алгоритма MD5 к не совсем случайным данным, в том числе системному времени и идентификатору процесса. В результате на тогдашнем оборудовании они смогли взломать реальный сеанс за 25 с. Сейчас это заняло бы меньше доли секунды. Вот так–то!

Компания Netscape придумала протокол SSL для использования в своем браузере (первая публичная версия называлась Netscape–designed Version 2). Ошибка содержалась в реализации, а не в самом проекте протокола, но ясно показала, что Netscape – это не та компания, которая может спроектировать безопасный транспортный протокол. Время подтвердило это мнение. Разработка версии 3 протокола была поручена профессиональному криптографу, который справился с задачей куда лучше.

Проблемы в OpenSSL

Совсем старые версии OpenSSL требовали, чтобы затравку для PRNG задавал пользователь. Если это не было сделано, то библиотека ограничивалась предупреждением «Random number generator not seeded» (генератор случайных чисел не затравлен). Некоторые программисты его игнорировали, и программа продолжала работать дальше. Другие задавали в качестве затравки фиксированную строку, программа и в этом случае была довольна.

Когда вошло в моду псевдоустройство /dev/random, для затравки PRNG стали использовать получаемое от него (вместо /dev/urandom) значение. В то время в ОС FreeBSD–Alpha еще не было устройства /dev/random, поэтому на данной платформе библиотека OpenSSL молчаливо продолжала работать по–старому (см. CVE CAN–2000–0535).

Потом обнаружились ошибки в PRNG, разработанном компанией Netscape (при определенных условиях противник мог вычислить состояние генератора и предсказать случайные числа). Это произошло, несмотря на то что в основе алгоритма лежала популярная криптографическая функция (см. CVE–2001–1141).

Раз уж такие ошибки возможны даже в распространенных криптографических API, вообразите, что может произойти, если вы решите самостоятельно разработать систему для генерации случайных чисел. На создание гарантированно безопасных генераторов затрачено очень много усилий. Если вам никак не обойтись без собственного, то хотя бы воспользуйтесь их плодами. В следующем разделе мы покажем, как это можно сделать.

Искупление греха

Как правило, мы рекомендуем применять системный CRNG–генератор. Есть лишь три исключения из этого правила: когда вы пишете программу для системы, в которой

такого генератора нет; когда имеется необходимость повторно воспроизвести поток случайных чисел и когда степень безопасности, гарантируемая системой, вас не устраивает (в частности, когда генерируются 192–или 256–битовые ключи в Windows с помощью стандартного криптографического провайдера).

Windows

В состав Windows CryptoAPI входит функция CryptGetRandom, которую может реализовать любой криптографический провайдер. Это CRNG–генератор, который система часто затравливает новой энтропией.

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

...

#include <wincrypt.h>

void GetRandomBytes(BYTE *pbBuffer, DWORD dwLen) {

HCRYPTPROV hProvider; /* Вы должны создать экземпляр провайдера */

if (!CryptAcquireContext(&hProvider, 0, 0, PROV_RSA_FULL,

CRYPT_VERIFYCONTEXT))

ExitProcess((UINT) -1);

if (!CryptGetRandom(hProvider, dwLen, pbBuffer))

ExitProcess((UINT) -1);

}

В предположении, что вы работаете с достаточно современной версией Windows, в которой вообще есть этот API (а это почти наверняка так), обращение к CryptGetRandom всегда завершается успешно. Но лучше оставить код в таком виде, поскольку другие провайдеры могут предоставлять реализацию, в которой ошибки возможны, например если генератор истинно случайных чисел не проходит тест FIPS.

Код для .NET

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

using System.Security.Cryptography;

...

try {

byte[] b = new byte[32];

new RNGCryptoServiceProvider.GetBytes(b);

// b содержит 32 байта случайных данных

} catch (CryptographyException e) {

// Ошибка

}

Или на VB.NET:

...

Imports System.Security.Cryptography;

Dim b(32) As Byte

Dim i As Short

Try

Dim r As new RNGCryptoServiceProvider

r.GetBytes

\' b содержит 32 байта случайных данных

Catch e As CryptographyException

\' Обработать ошибку

End Try

Unix

В системах Unix криптографический генератор случайных чисел работает так же, как файл. Случайные числа поставляются двумя специальными устройствами (обычно они называются /dev/random и /dev/urandom, но в OpenBSD это /dev/ srandom и /dev/urandom). Реализации отличаются, но характеристики более–менее схожи. Эти устройства устроены так, что позволяют получать ключи любого разумного размера, поскольку хранят некий очень большой «ключ», содержащий, как правило, гораздо больше 256 битов энтропии. Как и в Windows, эти генераторы часто меняют затравку, в которую включаются все интересные асинхронные события, к примеру перемещения мыши и нажатия на клавиши.

Разница между /dev/random и /dev/urandom довольно тонкая. Может возникнуть мысль, что первое – это интерфейс к истинно случайным числам, а второе – CRNG–генератор. Возможно, таково и было первоначальное намерение, но ни в какой реальной ОС оно не реализовано. На самом деле оба устройства–CRNG–генераторы. Более того, по существу, это один и тот же генератор. Единственная разница в том, что в /dev/random применяется некая метрика, позволяющая определить, есть ли опасность недостаточной энтропии. Метрика консервативна, и это, наверное, хорошо. Но на самом деле она настолько консервативна, что система может оказаться уязвимой для DoS–атак, особенно на серверах, где за консолью никто не сидит. Если у вас нет серьезных оснований полагать, что в системном CRNG–генераторе с самого начала не было ни одного непредсказуемого состояния, то пользоваться устройством /dev/random вообще не стоит. Мы рекомендуем работать только с /dev/urandom.

Поделиться с друзьями: