如何在 C 中使用 /dev/random 或 urandom?

Posted

技术标签:

【中文标题】如何在 C 中使用 /dev/random 或 urandom?【英文标题】:How to use /dev/random or urandom in C? 【发布时间】:2011-02-04 01:37:54 【问题描述】:

我想在 C 中使用 /dev/random/dev/urandom。我该怎么做?我不知道如何在 C 中处理它们,如果有人知道请告诉我如何。谢谢你。

【问题讨论】:

查看这篇内容丰富的文章,了解一些关于采用这条路线(伪)随机性的常见警告:insanecoding.blogspot.fi/2014/05/… 【参考方案1】:

一般来说,最好避免打开文件来获取随机数据,因为过程中有很多故障点。

在最近的 Linux 发行版上,getrandom 系统调用可用于获取加密安全随机数,并且它不会失败如果 GRND_RANDOM 未指定作为标志,读取量最多为256字节。

截至 2017 年 10 月,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);

【讨论】:

@karim:请永远不要从 /dev/random 读取所有字节。只是不要。您的程序可能不是系统上需要随机字节的唯一用户。 @zneak,对不起,我忘记了可以在此处编辑帖子。谢谢你让我知道。您的最后修改仅添加了读取检查,而不是循环。我对其进行了修改以使用循环,因此代码现在将适当地阻塞。 @morrog Overeager 审稿人拒绝了它,所以我手动进行了更改。很抱歉,您没有因此获得荣誉。 @CodesInChaos,我是 quoting the Linux manpage:“如果您不确定应该使用 /dev/random 还是 /dev/urandom,那么您可能想要使用后者。作为一般规则,除了长期存在的 GPG/SSL/SSH 密钥之外的所有内容都应该使用 /dev/urandom。" @zneak 强调长寿,因为对于那些更加偏执的人来说并没有什么坏处。对于普通加密使用/dev/urandom 很好。【参考方案2】:

上面还有其他准确的答案。不过,我需要使用FILE* 流。这就是我所做的......

int byte_count = 64;
char data[64];
FILE *fp;
fp = fopen("/dev/urandom", "r");
fread(&data, 1, byte_count, fp);
fclose(fp);

【讨论】:

可以通过简单地将 int 指针转换为 char 指针来直接读取 int。 fread((char*)(&amp;myInt),sizeof(myInt),1,fp) @AzeemBande-Ali:你为什么不使用 fread((int*)(&myInt),sizeof(myInt),1,fp) 代替?我的意思是转换为 int* ? 在任何情况下都不应在 C 代码中使用强制转换,fread() 需要一个 void *,所以只需执行 fread(&myInt, ... ); 为什么需要byte_count?没用过。 @CalculatorFeline 这里的 byte_count 有点混乱,op 最初可能希望每个索引都具有相同的字节长度,但这做不到...【参考方案3】:

只需打开文件进行读取,然后读取数据。在 C++11 中,您可能希望使用 std::random_device,它提供对此类设备的跨平台访问。

【讨论】:

std::random_device 似乎没有进入 2011 年标准。它确实出现在N3797 draft。 貌似std::random_device确实最终进入了C++11。 问题是 std::random_device 是 C++ 而不是 C,并且 OP 询问如何使用 /dev/random/dev/urandom 而不是如何使用 std::random_device 虽然这是一个不错的选择使用std::random_device,它有好处,这不是 OP 要求的【参考方案4】:

Zneak 100% 正确。读取比启动时所需的稍大的随机数缓冲区也很常见。然后,您可以在内存中填充一个数组,或者将它们写入您自己的文件以供以后重复使用。

上述的典型实现:

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

这或多或少有点像只前进的磁带,可以根据需要由另一个线程神奇地补充。 lot 和 services 中的一个 lot 提供大文件转储,只有随机数,这些随机数是由更强大的生成器生成的,例如:

放射性衰变 光学行为(光子撞击半透明镜子) 大气噪音(不如上述强) 农场里有一群醉醺醺的猴子敲键盘和移动老鼠(开玩笑)

不要将“预打包”熵用于加密种子,以防不言而喻。这些集合适合模拟,根本不适合用于生成密钥等。

不关心质量,如果您需要大量数字来进行蒙特卡罗模拟,最好以不会导致 read() 阻塞的方式提供它们。

但是,请记住,数字的随机性与生成它所涉及的复杂性一样具有确定性。 /dev/random/dev/urandom 很方便,但不如使用 HRNG(或从 HRNG 下载大型转储)强大。另外值得注意的是/dev/randomrefills via entropy,所以它可以根据情况阻塞一段时间。

【讨论】:

下载“只有随机数的大文件转储”对于密码学目的来说是一个糟糕的建议。它要求其他人为您的功能提供种子,而这些服务似乎通过互联网传输未加密的数据。请不要那样做。 @dequis 我澄清了。我认为使用它们来运行大型模拟没有问题,有点认为不将它们用于 keygen / 等是常识,但值得奇怪的是具体到这一点。这个问题与努力无关,所以我真的没想到这么具体,但很好。【参考方案5】:

zneak 的回答很简单,但实际情况比这更复杂。例如,您首先需要考虑 /dev/urandom 是否真的是随机数设备。如果您的机器被入侵并且设备被替换为 /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 博客 covered this, and other pitfalls;我强烈建议阅读整篇文章。我必须感谢他们从哪里提取了这个解决方案。

编辑添加 (2014-07-25)... 巧合的是,我昨晚读到,作为LibReSSL effort 的一部分,Linux 似乎获得了GetRandom() 系统调用。在撰写本文时,还没有关于它何时会在内核通用版本中可用的消息。然而,这将是获得加密安全随机数据的首选接口,因为它消除了通过文件访问提供的所有陷阱。另请参阅 LibReSSL possible implementation。

【讨论】:

攻击者有足够的能力将 /dev/random 或 /dev/urandom 替换为其他东西,通常也有足够的能力加载内核模块来搞砸你在确定它是否是随机设备与否。 man page 表示getrandom() 是在内核 3.17 中引入的。因此,截至 2018 年 1 月 17 日,库存的 Ubuntu 16.04 没有它。在终端中运行uname -a 以检查您的内核版本。

以上是关于如何在 C 中使用 /dev/random 或 urandom?的主要内容,如果未能解决你的问题,请参考以下文章

如何创建/dev/random和/dev/urandom字符设备

如何在 BusyBox shell 中生成随机数

hostapd/dev/random/dev/urandom

/dev/random和/dev/urandom的一点备忘

tomcat 启动慢解决(/dev/random)

/dev/random