Как использовать /dev/random или urandom в C?

Я хочу использовать /dev/random или /dev/urandom в C. Как мне это сделать? Я не знаю, как я могу справиться с ними в C, если кто-то знает, пожалуйста, скажите мне, как это сделать. Спасибо.


person stojance    schedule 03.04.2010    source источник
comment
Ознакомьтесь с этой очень информативной статьей о некоторых распространенных предостережениях на пути к (псевдо)случайности: insanecoding.blogspot.fi/2014/05/   -  person appas    schedule 17.06.2015


Ответы (5)


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

В последних дистрибутивах Linux для получить криптозащищенные случайные числа, и он не может дать сбой, если GRND_RANDOM не указан как флаг, а объем чтения составляет не более 256 байт.

По состоянию на октябрь 2017 года OpenBSD, Darwin и Linux (с -lbsd) теперь имеют реализацию arc4random, который является криптозащищенным и не может дать сбой. Это делает его очень привлекательным вариантом:

char myRandomData[50];
arc4random_buf(myRandomData, sizeof myRandomData); // done!

В противном случае вы можете использовать случайные устройства, как если бы они были файлами. Вы читаете их и получаете случайные данные. Здесь я использую open/read, но fopen/fread тоже подойдет.

int randomData = open("/dev/urandom", O_RDONLY);
if (randomData < 0)
{
    // something went wrong
}
else
{
    char myRandomData[50];
    ssize_t result = read(randomData, myRandomData, sizeof myRandomData);
    if (result < 0)
    {
        // something went wrong
    }
}

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

/dev/random более привередлив. На большинстве платформ он может возвращать меньше байтов, чем вы запросили, и может блокироваться, если доступно недостаточно байтов. Это делает историю обработки ошибок более сложной:

int randomData = open("/dev/random", O_RDONLY);
if (randomData < 0)
{
    // something went wrong
}
else
{
    char myRandomData[50];
    size_t randomDataLen = 0;
    while (randomDataLen < sizeof myRandomData)
    {
        ssize_t result = read(randomData, myRandomData + randomDataLen, (sizeof myRandomData) - randomDataLen);
        if (result < 0)
        {
            // something went wrong
        }
        randomDataLen += result;
    }
    close(randomData);
}
person zneak    schedule 03.04.2010
comment
хорошо, хороший ответ. А что, если я хочу настроить его на чтение нескольких номеров? Буду ли я делать while(something) {read(..)} или открывать/закрывать его каждый раз, когда цикл начинается заново? - person stojance; 03.04.2010
comment
Привет, не могли бы вы дать мне несколько советов, как я могу непрерывно читать все байты из \dev\random. если он блокирует, как я могу это почувствовать. Но опять же, когда будет доступно больше байтов, я их прочитаю. - person karim; 02.11.2010
comment
@karim Он не блокируется в Mac OS (которую я использую), и я не слишком уверен, как поступать с блочными файлами в этих обстоятельствах. Вы должны задать новый вопрос для него. - person zneak; 02.11.2010
comment
@karim: Пожалуйста, никогда не читайте все байты из /dev/random. Просто не надо. Ваша программа, вероятно, не единственный пользователь в системе, которому нужны случайные байты. - person Zan Lynx; 26.02.2011
comment
Мне нужно всего 256 байт. тогда я останавливаюсь. Но иногда у меня не получается 256 байт. Я должен ждать 1/2 секунды. - person karim; 26.02.2011
comment
Код в этом ответе в настоящее время неверен. Вам нужно проверить возвращаемое значение read, чтобы увидеть, сколько байтов было фактически прочитано, и зацикливаться, пока не получите достаточно. Как есть, этот код может оставить myRandomInteger заполненным старой памятью, а не случайными данными. - person morrog; 25.10.2013
comment
@morrog, я считаю, что мое редактирование решит поднятые вами проблемы. Также не забывайте, что вы можете отредактировать любой ответ, если считаете его неадекватным. - person zneak; 25.10.2013
comment
@zneak, извините, я забыл, что здесь можно редактировать сообщения. Спасибо, что дал мне знать. Ваша последняя модификация добавила только проверку на чтение вместо цикла. Я изменил его, чтобы использовать цикл, поэтому теперь код будет правильно блокироваться. - person morrog; 26.10.2013
comment
Рецензенты @morrog Overeager отклонили его, поэтому я внес изменения вручную. Жаль, что вам это не засчитывается. - person zneak; 27.10.2013
comment
На самом деле большинство криптографов рекомендуют использовать /dev/urandom. Если он был достаточно посеян один раз, он выдаст неограниченное количество псевдослучайных чисел, подходящих для использования в криптографии. Беспокоит только то, что первоначального посева может быть недостаточно. Менее случайная проблема применяется только в некоторых редких особых случаях, например, в начале процесса загрузки на встроенных устройствах или клонах виртуальных машин. - person CodesInChaos; 27.10.2013
comment
@CodesInChaos, я цитирую справочную страницу Linux: если вы не уверены, следует использовать /dev/random или /dev/urandom, тогда, вероятно, вы захотите использовать последний. Как правило, /dev/urandom следует использовать для всего, кроме долгоживущих ключей GPG/SSL/SSH. - person zneak; 27.10.2013
comment
@zneak Акцент на долгоживущий, поскольку для тех, кто не боится быть параноиком. Для обычного использования криптографии /dev/urandom подходит. - person CodesInChaos; 27.10.2013
comment
Однако вам также необходимо убедиться, что /dev/random (или /dev/urandom) на самом деле является устройством случайных чисел, а не разреженным файлом или символической ссылкой на /dev/zero (что могло произойти, если ваш компьютер был взломан). На insanecoding есть хорошая статья. .blogspot.co.uk/2014/05/, где описаны все подводные камни слепого использования /dev/{u}random. - person Chris J; 23.07.2014
comment
Нужно ли проверять возвращаемое значение из вызова open(2)? - person KyleWpppd; 02.01.2015
comment
@KyleWpppd, этот ответ изначально проверялся на отсутствие условий ошибки для простоты, но кто-то еще решил, что это достаточно важно для read, чтобы включить его. Я бы сказал, что в целом вы всегда должны проверять наличие ошибок. Кроме того, если безопасность вашего приложения критична, рекомендуется проверить, является ли файл символьным устройством. - person zneak; 02.01.2015
comment
Этот ответ сидит здесь уже 5 лет, но я считаю, что код неверен. Вы даже не использовали файловый дескриптор randomData, кроме его закрытия. Вы уверены, что это не должно читаться read(randomData ...? Показывает важность именования. - person BoppreH; 01.08.2015
comment
@BoppreH, вы, вероятно, будете рады узнать, что эта конкретная проблема существует всего два года (кто-то еще предложил изменение, которое я в итоге объединил сам, не будучи достаточно осторожным). Говоря о предложении правок, вы всегда можете исправить любую другую проблему, которую вы обнаружите. - person zneak; 01.08.2015
comment
В последнем абзаце этого ответа делается неверное предположение, описанное здесь: 2uo.de/ мифы о рандоме/#структура. На самом деле, и /dev/random, и /dev/urandom сидят за одним и тем же CSPRNG. (Ваш совет выбрать /dev/urandom вместо /dev/random остается в силе.) - person Sebastian; 23.03.2017

Есть другие точные ответы выше. Однако мне нужно было использовать поток FILE*. Вот что я сделал...

int byte_count = 64;
char data[64];
FILE *fp;
fp = fopen("/dev/urandom", "r");
fread(&data, 1, byte_count, fp);
fclose(fp);
person Dustin Kirkland    schedule 16.08.2012
comment
Int можно прочитать напрямую, просто приведя указатель int к указателю char. fread((char*)(&myInt),sizeof(myInt),1,fp) - person Azeem Bande-Ali; 25.05.2013
comment
@AzeemBande-Ali: Почему бы вам вместо этого не использовать fread((int*)(&myInt),sizeof(myInt),1,fp)? Я имею в виду приведение к int* ? - person Larry; 30.05.2014
comment
Ни в коем случае не следует использовать приведение типов в коде C, fread() принимает значение void *, поэтому просто выполните fread(&myInt, ... ); - person nos; 03.01.2015
comment
Зачем вам byte_count? Он не используется. - person CalculatorFeline; 10.08.2017
comment
@CalculatorFeline byte_count здесь немного сбивает с толку, оператор, возможно, изначально хотел, чтобы каждый индекс имел одинаковую длину в байтах, но это не сработало ... - person LinconFive; 19.03.2020

Просто откройте файл для чтения, а затем прочитайте данные. В C++11 вы можете использовать std::random_device, который обеспечивает межплатформенный доступ к таким устройствам.

person Tronic    schedule 03.04.2010
comment
Похоже, что std::random_device не попал в стандарт 2011 года. Он присутствует в проекте N3797. - person Keith Thompson; 02.01.2015
comment
Похоже, что std::random_device попал в С++11 в конце концов. - person legends2k; 26.06.2015
comment
Проблема в том, что std::random_device находится на C ++, а не на C, и ОП спросил, как использовать /dev/random или /dev/urandom, а не как использовать std::random_device, хотя это хороший выбор для использования std::random_device и у него есть преимущества, это просто не то, о чем просил ОП - person Nfagie Yansaneh; 08.08.2017

Zneak верен на 100%. Также очень распространено чтение буфера случайных чисел, который немного больше, чем вам нужно при запуске. Затем вы можете заполнить массив в памяти или записать их в свой собственный файл для последующего повторного использования.

Типичная реализация вышеизложенного:

typedef struct prandom {
     struct prandom *prev;
     int64_t number;
     struct prandom *next;
} prandom_t;

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

  • Радиоактивный распад
  • Оптическое поведение (фотоны попадают в полупрозрачное зеркало)
  • Атмосферный шум (не такой сильный, как выше)
  • Фермы пьяных обезьян, печатающих на клавиатуре, и двигающихся мышей (шучу)

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

Не заботясь о качестве, если вам нужно много чисел для чего-то вроде моделирования Монте-Карло, гораздо лучше иметь их доступными таким образом, чтобы не вызвать блокировку read().

Однако помните, что случайность числа столь же детерминирована, как и сложность, связанная с его генерацией. /dev/random и /dev/urandom удобны, но не так надежны, как использование HRNG (или загрузка большого дампа из HRNG). Также стоит отметить, что /dev/random пополняется с помощью энтропии, поэтому он может блокироваться довольно долго. пока в зависимости от обстоятельств.

person Tim Post♦    schedule 04.04.2010
comment
Загрузка больших файловых дампов, содержащих только случайные числа, является ужасным советом для криптографических целей. Он просит кого-то еще предоставить начальное значение для ваших функций, и эти службы, похоже, передают эти данные в незашифрованном виде через Интернет. Пожалуйста, не делай этого. - person dequis; 09.07.2014
comment
@dequis я уточнил. Я не вижу проблем с их использованием для запуска больших симуляций, вроде подумал, что было бы здравым смыслом не использовать их для кейгена и т. д., но стоит быть как ни странно конкретным. Вопрос был независимым от усилий, поэтому мне действительно не приходило в голову быть таким конкретным, но хорошим замечанием. - person Tim Post♦; 28.07.2015

ответ zneak охватывает это просто, однако на самом деле все сложнее. Например, вам нужно подумать, действительно ли /dev/{u}random является устройством случайных чисел. Такой сценарий может возникнуть, если ваша машина была скомпрометирована, а устройства заменены символическими ссылками на /dev/zero или разреженный файл. Если это произойдет, случайный поток теперь полностью предсказуем.

Самый простой способ (по крайней мере, в Linux и FreeBSD) — выполнить вызов ioctl на устройстве, который будет успешным, только если устройство является генератором случайных чисел:

int data;
int result = ioctl(fd, RNDGETENTCNT, &data); 
// Upon success data now contains amount of entropy available in bits

Если это выполняется до первого считывания случайного устройства, то есть честная ставка на то, что у вас есть случайное устройство. Так что ответ @zneak лучше расширить:

int randomData = open("/dev/random", O_RDONLY);
int entropy;
int result = ioctl(randomData, RNDGETENTCNT, &entropy);

if (!result) {
   // Error - /dev/random isn't actually a random device
   return;
}

if (entropy < sizeof(int) * 8) {
    // Error - there's not enough bits of entropy in the random device to fill the buffer
    return;
}

int myRandomInteger;
size_t randomDataLen = 0;
while (randomDataLen < sizeof myRandomInteger)
{
    ssize_t result = read(randomData, ((char*)&myRandomInteger) + randomDataLen, (sizeof myRandomInteger) - randomDataLen);
    if (result < 0)
    {
        // error, unable to read /dev/random 
    }
    randomDataLen += result;
}
close(randomData);

Блог Insane Coding осветил это, и другие подводные камни не так давно; Настоятельно рекомендую прочитать всю статью. Я должен отдать должное тому, откуда было взято это решение.

Отредактировано для добавления (2014-07-25)...
Кстати, прошлой ночью я прочитал это как часть усилия LibReSSL, Linux, похоже, получает GetRandom() системный вызов. На момент написания статьи неизвестно, когда он будет доступен в общем выпуске ядра. Однако это был бы предпочтительный интерфейс для получения криптографически безопасных случайных данных, поскольку он устраняет все ловушки, которые предоставляет доступ через файлы. См. также возможную реализацию LibReSSL.

person Chris J    schedule 23.07.2014
comment
Злоумышленник, обладающий достаточной мощностью, чтобы заменить /dev/random или /dev/urandom чем-то другим, обычно также имеет достаточную мощность, чтобы загрузить модуль ядра, чтобы испортить каждую вашу попытку определить, является ли это случайным устройством или нет. - person zneak; 02.01.2015
comment
справочная страница говорит, что getrandom() был представлен в ядре 3.17. Так что в стоковой Ubuntu 16.04 его нет по состоянию на 17 января 2018 года. Запустите uname -a в терминале, чтобы проверить версию вашего ядра. - person erapert; 17.01.2018