Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Практическая криптография

..pdf
Скачиваний:
6
Добавлен:
12.11.2023
Размер:
16.23 Mб
Скачать

10.5 Аккумулятор

193

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

10.5.2Пулы

Чтобы обновить начальное число генератора, события нужно накапли­ вать в пуле. Последний должен быть достаточно большим для того, чтобы злоумышленник не смог перебрать возможные значения содержащихся в пу­ ле событий. Обновление начального числа с помощью “достаточно большого” пула случайных событий сделает бессмысленной всю ту информацию о состо­ янии генератора, которой мог обладать злоумышленник. К сожалению, мы не знаем, сколько событий нужно накопить в пуле, прежде чем обновлять начальное число генератора. В Yarrow мы пытались решить эту проблему путем использования оценок энтропии и различных эвристических правил. Fortuna же поступает гораздо более удачным способом.

У нас есть 32 пула: P Q, ..., Рзь Теоретически каждый пул содержит стро­ ку байтов неограниченной длины. На практике же все обстоит немного подругому. Полученная строка будет использоваться лишь в качестве входных данных для функции хэширования. По этой причине реализациям ГПСЧ Fortuna не нужно сохранять строку неограниченной длины — они могут под­ считывать хэш-код строки по мере накопления данных в пуле.

Каждый источник энтропии распределяет свои случайные события меж­ ду пулами по циклическому принципу. Это гарантирует, что энтропия, полу­ ченная от каждого источника, будет распределена между пулами более или менее равномерно. Каждое случайное событие, попавшее в тот или иной пул, присоединяется к строке, которая уже содержится в этом пуле.

Мы будем обновлять начальное число генератора каждый раз, когда объем содержимого пула PQ станет достаточно большим. Процедуры обновления на­ чального числа будут пронумерованы как 1,2,3,... В зависимости от номера обновления г в процесс обновления включаются один или более пулов. Пул Pi участвует в обновлении, если 2* является делителем г. Таким образом, пул Р0 будет участвовать в каждом обновлении, пул Pi — в каждом втором об­ новлении, пул Р2 — в каждом четвертом обновлении и т.п. После того как пул принял участие в обновлении, его содержимое сбрасывается и заменяется пустой строкой.

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

194

Глава 10. Генерация случайных чисел

об источниках энтропии или же сам периодически генерирует ложные собы­ тия? В этом случае он, вероятно, знает о содержимом пула Ро достаточно, для того чтобы предсказать новое состояние генератора на основе его старо­ го состояния и выходных данных. Тем не менее, когда в обновлении будет участвовать пул Pi, он будет содержать в два раза больше данных, не пред­ сказуемых для злоумышленника, пул Р г ~ в четыре раза больше непредска­ зуемых данных и т.п. Таким образом, если хотя бы один источник случайных событий не является предсказуемым для злоумышленника, то независимо от количества событий, которые генерирует сам злоумышленник или о которых у него есть информация, у нас всегда будет пул, содержащий достаточно эн­ тропии для отражения атаки.

Скорость восстановления системы после раскрытия злоумышленником информации о внутреннем состоянии генератора зависит от интенсивности,

скоторой энтропия (являющаяся таковой и для злоумышленника) поступает

впулы. Если предположить, что скорость поступления энтропии является фиксированной и равна р, тогда через t секунд у нас будет pt бит энтро­ пии. За этот период времени каждый пул получит около pt/ 32 бит энтропии. Злоумышленник больше не сможет отслеживать внутреннее состояние гене­ ратора, если оно будет обновлено с помощью пула, содержащего более 128 бит энтропии. Здесь возможны два случая. Если перед следующей операцией об­ новления в пуле Ро наберется более 128 бит энтропии, безопасность генерато­ ра будет восстановлена. В этом случае быстрота восстановления безопасности зависит от того, сколько данных удастся собрать в пуле Ро, прежде чем про­ изойдет обновление. Второй случай — это когда сбрасывание пула Ро проис­ ходит слишком быстро из-за возникновения случайных событий, известных злоумышленнику (или сгенерированных им самим). Пусть t — это время меж­ ду двумя обновлениями состояния генератора. За это время пул Р» соберет 2хpt/32 бит энтропии. Отметим также, что этот пул будет участвовать в про­ цедуре обновления каждые 24 секунд. Восстановление безопасности генера­ тора произойдет при следующем обновлении его состояния с помощью перво­ го пула Рь для которого будет справедливо неравенство 128 < 2ipt/32 < 256. (Наличие верхней границы неравенства объясняется следующим фактом: ес­ ли бы число 2xpt/32 было больше или равно 256, это бы означало, что 128 бит энтропии накопилось еще в пуле Pj_i, а это противоречит тому, что первым таким пулом является Pj.) Из приведенного выше неравенства следует, что

10.5 Аккумулятор

195

а значит,

Другими словами, интервал времени между моментами восстановления (24) ограничен временем, необходимым для накопления 213 бит энтропии (8192/р). На первый взгляд число 213 выглядит довольно большим, однако его появление можно объяснить следующим образом. Чтобы восстановить безопасность генератора, требуется по крайней мере 27 бит энтропии. Если нам не повезет и обновление системы произойдет как раз перед тем, как мы набрали 27 бит в некотором пуле, придется воспользоваться следующим пу­ лом, который ко времени обновления соберет около 28 бит энтропии. И нако­ нец. мы разбиваем данные на 32 пула, что добавляет еще один множитель 25.

Это очень хороший результат. Наше решение отличается от идеального не более чем на множитель 64 (нам понадобится максимум в 64 раза больше случайности, чем требует идеальное решение). Это постоянный множитель, который гарантирует, что ситуация никогда не станет слишком плохой и без­ опасность генератора в конце концов будет восстановлена. Более того, нам не обязательно знать, сколько энтропии содержат наши события или сколько ин­ формации есть у злоумышленника. Именно этим Fortuna выгодно отличается от Yarrow. Отсутствие оценок энтропии, которые практически невозможно построить правильно, пошло новому решению только на пользу. Механизм обновления полностью автоматизирован; если случайные данные поступают с хорошей интенсивностью, генератор быстро восстановит свою безопасность. Если же случайные данные поступают медленно, процесс восстановления бу­ дет гораздо длительнее.

До сих пор мы никак не учитывали тот факт, что у нас есть только 32 пу­ ла. А что, если даже в пуле Р31 между двумя обновлениями не наберется достаточно энтропии для того, чтобы восстановить безопасность генератора? Это может произойти в том случае, если злоумышленник инициирует так много случайных событий, что генератор испытает 232 обновлений еще до того, как источники, не находящиеся под влиянием злоумышленника, успе­ ют сгенерировать 213 бит энтропии. Вообще говоря, это маловероятно, но, чтобы полностью предотвратить подобную ситуацию, можно ограничить ча­ стоту обновлений. Каждое новое обновление будет выполняться не ранее чем через 100 мс после предыдущего. Это ограничит частоту обновлений до 10 в секунду, а значит, пул Р32, если бы таковой существовал, был бы впер­ вые использован не ранее чем через 13 лет после начала работы генератора! Учитывая, что экономический и технологический срок жизни большинства современных компьютеров значительно меньше 10 лет, вполне достаточно и 32 пулов.

196

Глава 10. Генерация случайных чисел

10.5.3Вопросы реализации

Ниже приводится несколько соображений по поводу реализации аккуму­ лятора.

Р а с п р е д е л е н и е с о б ы т и й м е ж д у п у л а м и

Поступающие события должны каким-то образом распределяться меж­ ду пулами. Заниматься распределением событий мог бы и сам аккумулятор, но это опасно по следующей причине. Нам понадобится реализовать функ­ цию, которая будет передавать события аккумулятору. Вполне вероятно, что к этой же функции сможет обращаться и злоумышленник. Последний мо­ жет выполнять дополнительные вызовы функции каждый раз при генерации “настоящего” события, тем самым влияя на выбор пула, в который должно было бы поступить следующее “настоящее” событие. Если злоумышленнику удастся собрать все “настоящие” события в пуле Ро> то система пулов станет неэффективной и злоумышленник сможет осуществлять атаки на один пул. Если же все настоящие события окажутся собранными в пуле Р31, то они вообще никогда не будут использованы.

Наше решение состоит в том, чтобы каждый источник энтропии переда­ вал вместе с событием и номер пула, в который следует поместить это со­ бытие. В этом случае, чтобы повлиять на распределение событий, злоумыш­ леннику потребовался бы доступ к памяти программы, которая генерирует событие. Если же злоумышленник действительно обладает доступом тако­ го высокого уровня, тогда, вероятно, дискредитированным следует считать и сам источник энтропии.

Аккумулятор мог бы осуществлять проверку того, в правильном ли по­ рядке каждый источник энтропии распределяет свои события между пулами. Хорошие функции часто проверяют, правильно ли сформированы их входные параметры, поэтому на первый взгляд такая идея кажется довольно удач­ ной. Тем не менее в нашей ситуации не совсем понятно, как должен вести себя аккумулятор, если такая проверка выявит нарушения. Если весь ГПСЧ выполняется как пользовательский процесс, он мог бы сгенерировать неис­ правимую ошибку и завершить выполнение программы. Но зачем же лишать систему генератора псевдослучайных чисел только из-за неправильного пове­ дения одного источника энтропии? Если же ГПСЧ является частью ядра опе­ рационной системы, ситуация становится еще сложнее. Представим себе, что какой-нибудь драйвер генерирует случайные события, но не в состоянии от­ слеживать даже простенький 5-битовый циклический счетчик событий. Что должен делать аккумулятор? Возвратить код ошибки? Но если программист делает такие простые ошибки, он, скорее всего, не будет проверять коды воз­ врата. Остановить работу ядра? Это слишком радикальная мера, которая

10.5 Аккумулятор

197

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

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

В р е м я о б р а б о т к и с о б ы т и я

Желательно ограничить количество вычислений, которые могут выпол­ няться при передаче события аккумулятору. Большинство событий являются временными и генерируются драйверами реального времени. Драйверам не нужен аккумулятор, который слишком долго обрабатывает события.

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

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

198

Глава 10. Генерация случайных чисел

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

Чтобы обновление начального числа выполнялось непосредственно перед обработкой запроса на получение случайных данных, необходимо инкапсули­ ровать генератор. Другими словами, генератор будет скрыт, чтобы его нельзя было вызвать напрямую. Для этого в аккумулятор будет включена функция R A N D O M DATA с таким же интерфейсом, как и у P SEU DOR A N D O M DA TA . Это защитит систему от пользователей, пытающихся вызвать генератор напря­ мую и тем самым пропустить процесс обновления, над реализацией которого мы так долго трудились. Разумеется, пользователи все еще могут создавать собственные экземпляры генератора для каких-то своих целей.

Типичная функция хэширования наподобие SHAd-256 обрабатывает вхо­ дящие сообщения, разбивая их на блоки фиксированного размера. Если мы будем обрабатывать строку пула по мере накопления в буфере каждого сле­ дующего блока, обработка события ограничится не более чем хэшировани­ ем одного блока. К сожалению, данный подход имеет свои недостатки. Для повышения эффективности работы процессоров в современных компьюте­ рах применяется система кэшей. Одна из особенностей кэширования состоит в том, что скорость работы процессора повышается, когда он на протяжении некоторого времени работает с одними и теми же данными. Если обрабаты­ вать блоки случайных данных по одному, процессору придется каждый раз извлекать из памяти предыдущий промежуточный хэш-код строки и переме­ щать его в наиболее быстрый кэш. Если же обрабатывать несколько блоков сразу, перемещение хэш-кода в наиболее быстрый кэш будет выполняться только для первого блока строки. Остальные блоки будут обрабатываться намного быстрее из-за того, что хэш-код уже находится в кэше процессора. В целом производительность современных процессоров значительно повыша­ ется, если процессор работает в рамках небольшого цикла, а не тогда, когда он вынужден постоянно переключаться между различными частями кода.

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

10.5 Аккумулятор

199

10.5.4 Инициализация

Процедура инициализации, как всегда, очень проста. До сих пор речь шла только о генераторе и аккумуляторе, но функции, которые мы собира­ емся определять ниже, являются частью внешнего интерфейса всего ГПСЧ Fortuna. Слово PRNG в именах этих функций означает, что они применимы ко всему проекту в целом.

функция I N I T I A L I Z E ? RNG выход: 71 Состояние ГПСЧ

Поместим в каждый из 32 пулов пустую строку. for г = 0 , .. . }31 do

P i* — е

od

Установим значение счетчика обновлений равным 0.

R E S E E D C N T * - 0

Затем инициализируем генератор.

Q« - I N I T I A L I Z E G E N E R A T O R ( )

Упакуем состояние.

71« - (iQ, R E S E E D C N T , Ро,. • . , Р31) return 7Z

10.5.5 Получение случайных данных

Как уже отмечалось, функция R A N D O M D A T A представляет собой оболоч­ ку для компонента-генератора. Структура этой функции будет не слишком простой, так как последняя должна обрабатывать обновление начального числа генератора.

ф у н к ц и я R A N D O M D A T A

вход: 71 Состояние ГПСЧ; изменяется этой функцией.

пКоличество байт случайных данных, которое необходимо сгенерировать.

выход: г Псевдослучайная строка байтов.

i f length(Po) > M I N P O O L S I Z E Л последнее обновление > 1 0 0 м с н а з а д

t h e n

Нам нужно выполнить обновление.

R E S E E D C N T *- R E S E E D C N T + 1

Объединим в одну строку хэш-коды всех пулов, которые следует использовать в этой операции обновления.

8 «— б

E S E E D

200

Глава 10. Генерация случайных чисел

for г = 0 ,..., 31 d o

if 2*|R ESEEDCN T then

s «- s II SHAd - 256(Pi)

P i* — c

fi

o d

Получив данные, мы можем выполнить обновление.

R ESEED(<?, S )

fi

Обновление, если таковое требовалось, выполнено. Теперь в дело мо­ жет вступать генератор, состояние которого является частью TZ.

return P SEUDOR A N D O M DA TA (^,7I)

Вначале функция R A N D OM DATA сравнивает размер пула Ро с парамет­ ром M INP O O L SIZE, чтобы узнать, нужно ли выполнять обновление. Мы мо­ жем использовать оптимистическую оценку того, каким должен быть размер пула, чтобы он мог содержать 128 бит энтропии. Предполагая, что каждое событие содержит 8 бит энтропии и занимает 4 байта в пуле (как вы помните, это соответствует 2байтам данных события), разумным значением параметра M INP O O L SIZE можно считать 64 байт. Вообще-то точное значение этого па­ раметра не играет особой роли, хотя использовать значение, меньшее 32 байт, все же не рекомендуется. Не следует выбирать и слишком большое значение, потому что тогда обновление будет отложено на слишком долгий срок даже при наличии очень хороших источников энтропии.

Следующий шаг состоит в увеличении счетчика обновлений. В процессе инициализации счетчику R C N T было присвоено значение 0, поэтому первое обновление будет пронумеровано как 1. Это автоматически гаранти­ рует, что в первой операции обновления, как мы и планировали, будет участ­ вовать только пул Ро-

Цикл fo r . . . do выполняет конкатенацию хэш-кодов строк, содержа­ щихся в пулах. Мы могли бы выполнять конкатенацию и самих строк пу­ лов, однако в этом случае программе вместо простого хэширования содер­ жимого каждого пула пришлось бы сохранять все строки полностью. Запись 2*|RE SEEDC N T означает проверку делимости. Это выражение истинно, если 2* является делителем значения R ESEEDCN T. Дотошные читатели, конечно же, обратят внимание на следующий факт: как только условие делимости не будет выполняться для какого-то г, оно не будет выполняться и для всех следующих г, поэтому все оставшиеся итерации цикла заведомо не нужны. Как видите, наша программа определенно требует оптимизации.

10.5 Аккумулятор

201

10.5.6Добавление события

Источники энтропии вызывают функцию A D D R A ND O M EV E N T , когда у них появляется очередное случайное событие. Обратите внимание, что каж­ дый источник энтропии идентифицируется уникальным номером. Мы не бу­ дем описывать, как система выделяет номера источников, так как это зависит от конкретной ситуации.

ф ун к ц и я A D D R A N D O M EV EN T

вход: 1Z Состояние ГПСЧ; изменяется этой функцией.

sНомер источника в диапазоне 0 ,..., 255.

гНомер пула в диапазоне 0 ,..., 31. Каждый источник должен распределять свои события между пулами по циклическому принципу.

еДанные события. Строка байтов, длина которой находится в диапазоне 1 ,..., 32.

Вначале выполним проверку параметров.

assert 1 < length(e) < 32 Л 0 < s < 255 Л 0 < г < 31

Добавим данные в пул. Pi <- Pi |s |length(e) |e

Напомним, что каждое событие кодируется с помощью 2+ length(e) байт, причем на каждую из величин s и Iength(e) отводится по одному байту. После этого полученная конкатенация присоединяется к содержимому пула. Об­ ратите внимание, что в наших примерах мы просто присоединяем данные к содержимому пула и не выполняем никакого хэширования. Хэширование содержимого пула происходит только тогда, когда пользователь запрашивает случайные данные. В реальной системе хэширование данных должно выпол­ няться “на лету” по мере их поступления. Это функционально эквивалентно нашему решению, к тому же проще в реализации, однако непосредственно описывать этот процесс в книге было бы гораздо сложнее.

Мы ограничили длину данных события 32 байтами. События большей длины довольно бесполезны. Источники энтропии должны передавать акку­ мулятору не все свои данные, а лишь те несколько байтов, которые и со­ держат непредсказуемые случайные данные. Если же энтропия разбросана по большому объему данных, источник должен провести предварительное хэширование передаваемых данных. Функция A DDR AND OM EVEN T должна выполняться как можно быстрее. Это особенно важно, поскольку многие ис­ точники энтропии в силу своей специфики работают в режиме реального времени. Такие источники не могут тратить слишком много драгоценного времени на вызов функции A D D R A ND O M EV E N T . Даже если источник выдает небольшие события, он не должен ждать, пока функция A D D RA N D O M EV E N T

202

Глава 10. Генерация случайных чисел

разберется с другими источниками, события которых имеют больший размер. Большинству реализаций понадобится также сериализовать вызовы функции A D D R A ND O M EV E N T с помощью мьютекса (mutex object), чтобы гарантиро­ вать, что в один и тот же момент времени к аккумулятору будет добавлено только одно событие5.

У некоторых источников энтропии может совсем не оказаться времени на вызов функции A D D R A N D O M EV E N T . В этом случае события можно сохра­ нять в буфере и реализовать отдельный процесс, который будет извлекать события из буфера и передавать их аккумулятору.

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

10.6 Управление файлом начального числа

Наш ГПСЧ умеет собирать энтропию и генерировать случайные данные после первого обновления случайного числа. Тем не менее, если мы перезагру­ зим компьютер, придется подождать, пока источники энтропии сгенерируют достаточно случайных событий для выполнения первого обновления. Кроме того, у нас нет гарантий, что состояние генератора после первого обновления не будет предсказуемым для злоумышленника.

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

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

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