Зачем использовать класс С# System.Random вообще, а не System.Security.Cryptography.RandomNumberGenerator?

Почему кто-нибудь будет использовать "стандартный" генератор случайных чисел из System.Random вообще, а не всегда использовать криптографически безопасный генератор случайных чисел от System.Security.Cryptography.RandomNumberGenerator (или его подклассы, поскольку RandomNumberGenerator является абстрактным)?

Нейт Лоусон рассказывает нам в своей презентации Google Tech Talk "" Crypto Strikes Back" в минуту 13:11, чтобы не использовать "стандартный", генераторы случайных чисел из Python, Java и С# и вместо этого использовать криптографически безопасную версию.

Я знаю разницу между двумя версиями генераторов случайных чисел (см. вопрос 101337).

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

Ответ 1

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

Ответ 2

Помимо скорости и более полезного интерфейса (NextDouble() и т.д.), также можно сделать повторяемую случайную последовательность, используя фиксированное начальное значение. Это очень полезно, в том числе во время тестирования.

Random gen1 = new Random();     // auto seeded by the clock
Random gen2 = new Random(0);    // Next(10) always yields 7,8,7,5,2,....

Ответ 3

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

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

Дефект 1: посев

Серийный конструктор по умолчанию использует текущее время. Таким образом, все экземпляры Random, созданные с помощью конструктора по умолчанию в течение короткого периода времени (около 10 мс), возвращают одну и ту же последовательность. Это документировано и "по дизайну". Это особенно раздражает, если вы хотите многопоточность кода, поскольку вы не можете просто создать экземпляр Random в начале каждого выполнения потока.

Обходной путь должен быть особенно осторожным при использовании конструктора по умолчанию и при необходимости вручную при необходимости.

Другая проблема заключается в том, что пространство семян довольно мало (31 бит). Поэтому, если вы создаете 50k экземпляров Random с совершенно случайными семенами, вы, вероятно, получите одну последовательность случайных чисел дважды (из-за парадоксальный день рождения). Таким образом, ручной посев также нелегко получить.

Ошибка 2: распределение случайных чисел, возвращаемых Next(int maxValue), смещено

Существуют параметры, для которых Next(int maxValue) явно неравномерен. Например, если вы подсчитаете r.Next(1431655765) % 2, вы получите 0 примерно в 2/3 образцов. (Пример кода в конце ответа.)

Недостаток 3: метод NextBytes() неэффективен.

Стоимость каждого байта NextBytes() примерно такая же, как и стоимость генерации полного целочисленного образца с помощью Next(). Из этого я подозреваю, что они действительно создают один образец за каждый байт.

Лучшая реализация с использованием 3 байтов из каждого образца ускорит NextBytes() почти на 3.

Благодаря этому недостатку Random.NextBytes() только на 25% быстрее, чем System.Security.Cryptography.RNGCryptoServiceProvider.GetBytes на моей машине (Win7, Core i3 2600MHz).

Я уверен, что если кто-то проверит исходный/декомпилированный байт-код, они найдут еще больше недостатков, чем я обнаружил в своем анализе в черном ящике.


Примеры кода

r.Next(0x55555555) % 2 сильно предвзято:

Random r = new Random();
const int mod = 2;
int[] hist = new int[mod];
for(int i = 0; i < 10000000; i++)
{
    int num = r.Next(0x55555555);
    int num2 = num % 2;
    hist[num2]++;
}
for(int i=0;i<mod;i++)
    Console.WriteLine(hist[i]);

Производительность:

byte[] bytes=new byte[8*1024];
var cr=new System.Security.Cryptography.RNGCryptoServiceProvider();
Random r=new Random();

// Random.NextBytes
for(int i=0;i<100000;i++)
{
    r.NextBytes(bytes);
}

//One sample per byte
for(int i=0;i<100000;i++)
{   
    for(int j=0;j<bytes.Length;j++)
      bytes[j]=(byte)r.Next();
}

//One sample per 3 bytes
for(int i=0;i<100000;i++)
{
    for(int j=0;j+2<bytes.Length;j+=3)
    {
        int num=r.Next();
        bytes[j+2]=(byte)(num>>16);   
        bytes[j+1]=(byte)(num>>8);
        bytes[j]=(byte)num;
    }
    //Yes I know I'm not handling the last few bytes, but that won't have a noticeable impact on performance
}

//Crypto
for(int i=0;i<100000;i++)
{
    cr.GetBytes(bytes);
}

Ответ 4

System.Random намного более эффективен, так как он не генерирует криптографически безопасные случайные числа.

Простой тест на моей машине, заполняющий буфер размером 4 байта случайными данными, 1,000,000 раз занимает 49 мс для Random, но 2845 мс для RNGCryptoServiceProvider. Обратите внимание: если вы увеличиваете размер буфера, который вы заполняете, разница сужается, поскольку накладные расходы для RNGCryptoServiceProvider менее актуальны.

Ответ 5

Наиболее очевидные причины уже упоминались, поэтому здесь более неясным: криптографические PRNG обычно должны постоянно поддаваться "реальной" энтропии. Таким образом, если вы слишком часто используете CPRNG, вы можете истощить пул энтропийных систем, который (в зависимости от реализации CPRNG) либо ослабит его (что позволит злоумышленнику предсказать его), либо он будет блокироваться при попытке заполнить его энтропийный пул (таким образом, он становится вектором атаки для атаки DoS).

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

Это реальная проблема реального мира, BTW, которая наблюдалась на безгласных серверах (которые, естественно, имеют довольно небольшие пулы энтропии, потому что им не хватает энтропийных источников, таких как ввод мыши и клавиатуры) под управлением Linux, где приложения неправильно используют /dev/random kernel CPRNG для всех видов случайных чисел, тогда как правильное поведение должно состоять в том, чтобы прочитать небольшое начальное значение из /dev/urandom и использовать его для того, чтобы выровнять их собственный PRNG.

Ответ 6

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

Ответ 7

Это обсуждалось довольно подробно, но в конечном счете проблема производительности является вторичным соображением при выборе RNG. Существует огромное количество RNG, а консервированный Lehmer LCG, который состоит из большинства системных RNG, не самый лучший и даже не самый быстрый. На старых, медленных системах это был отличный компромисс. Этот компромисс редко бывает актуальным в наши дни. Эта вещь сохраняется в современных системах прежде всего потому, что: A) вещь уже построена, и в этом случае нет реальной причины "изобретать колесо", а B) для того, для чего огромная масса людей будет ее использовать, "достаточно хорошо".

В конечном счете, выбор RNG сводится к соотношению риска/вознаграждения. В некоторых приложениях, например, в видеоигре, нет никакого риска. RHG Lehmer более чем достаточен и мал, кратким, быстрым, хорошо понятным и "в коробке".

Если приложение представляет собой, например, онлайн-покерную игру или лотерею, где есть реальные призы, и реальные деньги вступают в игру в какой-то момент в уравнении, "в поле" Lehmer больше не является адекватным. В 32-битной версии он имеет только 2 ^ 32 возможных допустимых состояния, прежде чем он начнет цикл в лучшем случае. В эти дни, что открытая дверь в грубую силу атаки. В подобном случае разработчик захочет перейти к чему-то вроде RHG Very Long Period некоторых видов и, возможно, запустит его у криптографически сильного провайдера. Это дает хороший компромисс между скоростью и безопасностью. В таком случае человек будет искать что-то вроде Mersenne Twister или многократного рекурсивного генератора.

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

Ответ 9

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

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

Ответ 10

Если мне не нужна безопасность, т.е. я просто хочу, чтобы относительно неопределенное значение не было криптографически сильным, Random имеет гораздо более легкий интерфейс для использования.

Ответ 11

Различные потребности требуют разных ГСЧ. Для криптования вы хотите, чтобы ваши случайные числа были как можно более случайными. Для моделирования Монте-Карло вы хотите, чтобы они заполнили пространство равномерно и чтобы запустить RNG из известного состояния.

Ответ 12

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

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

Если вы хотите улучшить "случайность", не жертвуя интерфейсом, вы можете наследовать от System.Random, переопределяя несколько методов.

Зачем вам нужна детерминированная последовательность

Одна из причин иметь детерминированную последовательность, а не истинную случайность, состоит в том, что она повторяема.

Например, если вы используете численное моделирование, вы можете инициализировать последовательность с (истинным) случайным числом и записать, какой номер был использован.

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

Зачем вам нужна эта, не очень хорошая, последовательность

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

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

Ответ 13

Я написал игру (Crystal Sliders на iPhone: Здесь), которая выложила бы "случайную" серию из драгоценных камней (изображений) на карте, и вы поменяли бы карту, как вы ее хотели, и выберите их, и они ушли. - Как и в Bejeweled. Я использовал Random(), и он был засеян числом 100ns тиков с момента загрузки телефона, довольно случайным семени.

Мне показалось удивительным, что он будет генерировать игры, которые были почти идентичны друг другу - из 90 или около того драгоценных камней, из двух цветов, я бы получил два ТОЧНО одинаковые, за исключением от 1 до 3 драгоценных камней! Если вы переворачиваете 90 монет и получаете одинаковые рисунки, за исключением 1-3 флип, это очень маловероятно! У меня есть несколько снимков экрана, которые показывают их одинаково. Я был шокирован тем, насколько плох System.Random() был! Я предположил, что я ДОЛЖЕН написать что-то ужасное в своем коде и неправильно использовал его. Я был не прав, хотя это был генератор.

Как эксперимент - и окончательное решение, я вернулся к генератору случайных чисел, который я использовал с 1985 года или около того, - что лучше всего. Это быстрее, имеет период 1.3 * 10 ^ 154 (2 ^ 521), прежде чем он повторится. Первоначальный алгоритм был засеян 16-разрядным номером, но я изменил его на 32-битное число и улучшил начальное посев.

Оригинальный находится здесь:

ftp://ftp.grnet.gr/pub/lang/algorithms/c/jpl-c/random.c

С годами я бросил каждый случайный номер теста, о котором я мог подумать, и он прошел мимо всех них. Я не ожидаю, что он имеет значение как криптографический, но он возвращает число так же быстро, как "return * p ++;" пока он не закончится из 521 бит, а затем он быстро выполняет бит над битами для создания новых случайных.

Я создал оболочку С#, назвав ее JPLRandom(), реализовал тот же интерфейс, что и Random(), и изменил все места, где я назвал их в коде.

Разница была VASTLY лучше - OMG Я был поражен - не должно быть никакого способа, я мог бы сказать, просто глядя на экраны из 90 или около того драгоценных камней в шаблоне, но я сделал экстренный выпуск моей игры после этого.

И я бы никогда больше не использовал System.Random() для чего-либо еще. Я ПОЗВОЛЯЮ, что их версия сбрасывается чем-то, кому сейчас 30 лет!

-Traderhut Games