C编程-随机数获取

Posted by 周思进 on September 18, 2019

想要获取伪随机数,可以通过接口rand或者random生成,也可以通过读取/dev/random或者/dev/urandom文件读取。

直接贴一段代码

/** brief		获取指定字节的随机数(不超过32)
 * @param[in]
 * param[out]
 * return
 */
int get_random(char *buf, unsigned int len)
{
	FILE *f = NULL;
	char *p = NULL;
	int i = 0;
	int j = 0;
	static int tmp_seed = 0;

	if (NULL == buf || len > 32)
	{
		printf("arg is wrong.");
		return -1;
	}

	// 从这个文件每次读取的内容都不一样
	f = fopen("/dev/urandom", "rb");
	if (f != NULL)	// 需要考虑系统没有这个文件的情况
	{
		fread(buf, len, 1, f);
		fclose(f);
	}
	else
	{
		p = buf;
		tmp_seed++;

		srand(time(NULL) + tmp_seed);
		for (i = 0; i < 32; i++)
		{
			*p++ = (char)random();
		}
	}

	for (i = 0; i < 32; i++)
	{
		// 2x 表示16进制输出内容占2位,这里需要转成无符号类型打印,否则大于127的整型数据就会被当成负数显示
		printf("%2x ", (unsigned char)buf[i]);	
		j++;
		if (j % 16 == 0)
		{
			j = 0;
			printf("\n");
		}
	}

	printf("\n");

	return 0;
}


通过man手册查看rand和random接口声明如下

int rand(void);
void srand(unsigned int seed);
long int random(void);
void srandom(unsigned int seed);

random接口比rand返回的数值范围要大

从手册中查看,也是推荐使用random接口

The versions of rand() and srand() in the Linux C Library use the same random number generator as random(3) and srandom(3), so the lower-order bits should be as random as the higher-order bits. However, on older rand() imple‐ mentations, and on current implementations on different systems, the lower-order bits are much less random than the higher-order bits. Do not use this function in applications intended to be portable when good randomness is needed. (Use random(3) instead.)


一般会取time接口返回值来做seed值,需要注意的是

These sequences are repeatable by calling srandom() with the same seed value

而time的精度是秒,这很有可能在1S内多次调用这个接口,导致生成的序列号是一样的

所以示例代码加个tmp_seed值,每次接口进来加的值也不一样,保证生成的序列号不一样


另外这个srand在调用random之前,只需要设置一次就行,而不是每次调用random接口都需要进行设置,如果每次都设置一样 的seed值,那么random返回的接口也一直是一样的,也可以通过man手册查看rand的实现示例,可以看出前一次的rand中生成 的数会作为下一次random调用。

static unsigned long next = 1;

/* RAND_MAX assumed to be 32767 */
int myrand(void) 
{
    next = next * 1103515245 + 12345;
    return((unsigned)(next/65536) % 32768);
}

void mysrand(unsigned seed) 
{
    next = seed;
}


If no seed value is provided, the random() function is automatically seeded with a value of 1.

如果不调用srand接口,那么进程第一次调用random接口,默认使用1作为seed值,这样就会出现,每次进程重启第一次获取的 随机数都是一样的。


对于random文件读取,直接从man手册摘了几段如下:

When read, the /dev/random device will only return random bytes within the estimated number of bits of noise in the entropy pool. /dev/random should be suitable for uses that need very high quality randomness such as one-time pad or key generation. When the entropy pool is empty, reads from /dev/random will block until additional environmental noise is gathered.

Since reads from /dev/random may block, users will usually want to open it in nonblocking mode (or perform a read with timeout), and provide some sort of user notification if the desired entropy is not immediately available.

A read from the /dev/urandom device will not block waiting for more entropy. As a result, if there is not suffi‐ cient entropy in the entropy pool, the returned values are theoretically vulnerable to a cryptographic attack on the algorithms used by the driver. Knowledge of how to do this is not available in the current unclassified literature, but it is theoretically possible that such an attack may exist. If this is a concern in your application, use /dev/random instead

While some safety margin above that minimum is reasonable, as a guard against flaws in the CPRNG algorithm, no cryp‐ tographic primitive available today can hope to promise more than 256 bits of security, so if any program reads more than 256 bits (32 bytes) from the kernel random pool per invocation, or per reasonable reseed interval (not less than one minute), that should be taken as a sign that its cryptography is not skilfully implemented.


如果需要高质量的随机数,可以从/dev/random文件读取,但如果entropy pool is empty,则读操作会阻塞,如果不想阻塞住,可以考虑非阻塞方式打开文件

fd = open("/dev/random", O_RDONLY | O_NONBLOCK);

程序再针对相应的错误进行处理。

/dev/urandom则不存在阻塞问题,不过就可能存在随机数不够安全的可能,所以如果安全等级要求高,应该考虑读取/dev/randdom,如果要求不高,一般直接读取/dev/urandom即可。另外一次读取字节长度最好不要超过32字节。