多线程安全的随机数生产函数
string GenerateRandomString(int nLen)
{
std::string strRet;
const char* cArray = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int nArrayLen = strlen(cArray);
//设置当前时间为种子
srand(::time(NULL));
for (int i = 0; i < nLen; i++)
{
int nIndex = rand() % nArrayLen;
strRet += cArray[nIndex];
}
}
(二)解决方案
1.使用GUID来生产随机字符串,保证多线程下是安全的。这种方法和函数的本意虽然有些偏离,但是在软件中这个确实是解决了问题。那如果一定要保持函数原型不变,需要生成有数字和字母组成的随机的指定字符个数的字符串,该怎么办呢?第二种方法可以解决。
string GenerateRandomString()
{
::CoInitialize(NULL);
CoCreateGuid(&guid);
StringCchPrintfA(
buff, 64, ("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x"),
guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1],
guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5],
guid.Data4[6], guid.Data4[7]);
return string(buff);
}
2.使用rand_s代替rand函数,这个函数是不依赖种子的更安全的伪随机数生成器。并且是线程安全的。
{
std::string strRet;
const char* cArray = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int nArrayLen = strlen(cArray);
for (int i = 0; i < nLen; i++)
{
unsigned uRandomValue = 0;
rand_s(&uRandomValue);
int nIndex = uRandomValue % nArrayLen;
strRet += cArray[nIndex];
}
}
(三)rand以及rand_s函数的实现(vs2017)
1.rand函数实现
extern "C" void __cdecl srand(unsigned int const seed)
{
__acrt_getptd()->_rand_state = seed;
}
extern "C" int __cdecl rand()
{
__acrt_ptd* const ptd = __acrt_getptd();
return (ptd->_rand_state >> 16) & RAND_MAX;
}
可以看出,srand函数设置的种子保存在线程本地存储(TLS)中的_rand_state中,而rand函数就是对这个值进行了简单的数学运算然后又赋值给了_rand_state。可以看出这种方式生成的随机数是确定的,也就是说相同的种子返回相同的随机数序列。
从这里也看到了随机数的种子是和线程相关的,每个线程是隔离的,多线程代码执行确实是安全的。但是问题是种子如果一样了生成的随机数就一样了。
顺便提下_rand_state的默认值是1
2.rand_s函数实现
{
_VALIDATE_RETURN_ERRCODE(result != nullptr, EINVAL);
*result = 0;
{
errno = ENOMEM;
return errno;
}
}
对比java来说:
rand相当于Random类
rand_s相当于SecureRandom
(四)真随机数和伪随机数
先分享一个真随机数的网站: https://www.random.org/
真正的随机数是使用物理现象产生的:比如掷钱币、骰子、转轮、使用电子元件的噪音、核裂变等等,这样的随机数发生器叫做物理性随机数发生器,它们的缺点是技术要求比较高。
随机函数是按照一定算法模拟产生的,其结果是确定的,是可见的。我们可以这样认为这个可预见的结果其出现的概率是100%。所以用计算机随机函数所产生的“随机数”并不随机,是伪随机数。
https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator
这篇文章中讲到了真随机数,不过我个人的理解是秘密学安全的随机数应该都是属于真随机数。
所以,rand是伪随机数,rand_s是真随机数。
(五) c++11生产随机数的方法
可以参考下面这些文章:
https://docs.microsoft.com/en-us/cpp/standard-library/random?view=vs-2019
https://docs.microsoft.com/en-us/cpp/standard-library/random-device-class?view=vs-2019
总结来说
random_device是真随机数
mt19937是伪随机数。
两者的区别:
There are two highly useful URNGs in Visual Studio—mt19937
and random_device
—as shown in this comparison table:
使用示例:
1.random_device
(六)各大开源软件如何使用随机数
1.chromiuim
char* output_ptr = static_cast<char*>(output);
while (output_length > 0) {
const ULONG output_bytes_this_pass = static_cast<ULONG>(std::min(
output_length, static_cast<size_t>(std::numeric_limits<ULONG>::max())));
const bool success =
RtlGenRandom(output_ptr, output_bytes_this_pass) != FALSE;
CHECK(success);
output_length -= output_bytes_this_pass;
output_ptr += output_bytes_this_pass;
}
}
2.Qt
https://doc.qt.io/qt-5/qrandomgenerator.html#securelySeeded
https://code.qt.io/cgit/qt/qtbase.git/tree/src/corelib/global/qrandom.cpp?h=dev
Qt中提供真随机数是使用rand_s作为fallback机制(兜底),真随机数依赖cpu信息,没有深入研究了。
3.mozilla
也是从看Qt代码才在注释中发现mozilla代码的。
https://hg.mozilla.org/mozilla-central/file/722fdbff1efc/security/nss/lib/freebl/win_rand.c#l146
直接使用了RtlGenRandom作为随机数的实现。
(七)参考资料
https://www.random.org/
https://blog.csdn.net/czc1997/article/details/78167705
https://blog.csdn.net/hahachenchen789/article/details/84251345
https://www.iteye.com/blog/kevin-hust-744284
https://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful