查看“Random Number Generator”的源代码
←
Random Number Generator
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
随机数生成器(RNG)可以用很多不同的方式实现。 本文解释了其中一些方式。 ==熵(Entropy)== 计算机是确定性设备。 如果程序相同且所有输入相同,则每次计算的结果都相同。 那么,计算机如何生成随机数呢? 计算机不可以是随机的,但它周围的物理世界可以。 许多物理事件在某种程度上是随机的,或者更严格地说,具有某种程度的熵。 即使在地球上最好的屏蔽录音室里,录音也会包含一定程度的噪音。 人类在键盘上输入的时间不一致,并且输入准随机文本,比如你正在阅读的文章。 甚至其他计算机也可以提供熵:虽然大多数网络流量完全是机器生成的,但最终网络数据包的确切相对时间来自按下电源按钮的那一刻,这是另一种人类行为。 因此,输入事件的时间和性质可以为我们提供一些熵。 ==随机数生成器的类型== 有多种方法可以划分随机数生成器;一种是通过数字的生成方式,另一种是通过数字的使用方式。 这些是重要的区别:前者可能会影响操作系统的设计,后者可能会影响系统的安全性。 ===随机数的来源=== 有两种随机数:“真”随机数和伪随机数。 对于“真”随机数生成,系统持续测量预期为随机的特定事件集。 这可以是宇宙辐射和原子衰变,也可以是用户输入的时间和时钟抖动。 测量是去偏(de-biased)的,并“搅拌”到一个<em>熵池中,从中可以提取随机数。 伪随机数由一种算法(<em>PRNG</em>)生成,该算法转换一些内部状态,并根据请求计算输出值。 可以设置初始种子,但在此之后,下一个状态仅取决于前一个状态。 有许多不同的PRNG,下面讨论其中一些。 可以使用一些“真”随机数来为伪随机生成器的状态设定种子,但这并不能使PRNG“真随机”。 根据具体的算法,预测所有下一个输出可能与预测前一个输出一样简单。 正如下一节所讨论的,这可能会产生严重的影响。 ===随机数的应用=== 大致有两种应用:密码学和其他方面。 为了理解以下部分,我们首先应该回顾一下关于随机数生成器的加密安全意味着什么。 密码学家将安全分为两类:信息论安全和计算安全。 前者是更有力的主张。 如果密码系统在理论上是安全的,那么攻击者在不拥有密钥的情况下,就不能得出任何关于加密消息的信息。 这种密码系统很少见。 由于攻击者不能得出“任何东西”,因此必须隐藏所有内容,包括模式、时间甚至长度。 一个人怎么能隐藏信息的长度呢? 唯一的选择是根本不发送消息,这违背了密码系统的目的,或者发送无限长的消息。 所以一旦你开始发送,你就永远不会停止。 这就是为什么计算安全性较弱,但更实用的一种:密文必须很难找到,“给定一些合理的计算能力”。 选择这一限制的目的是希望在未来几十年内不会达到。 这还允许用户“泄露”有关密文的一些信息(例如,长度),只要找到原始输入仍然困难。 这些系统通常使用一些很难反转的操作,除非已知密钥,例如[https://en.wikipedia.org/wiki/RSA_(cryptosystem) 大素数分解]。 很明显,密钥不能是可预测的:如果对手知道下一个输出是什么,他们很容易猜测密钥,直到找到正确的密钥。 “真”随机数是加密安全随机性的最佳来源,但是很难建立一个大的、快速补充的熵池,其中每个源都是完全无偏的,并且不受其他源的影响。 要以难以逆转的方式“拉伸”熵,可以使用加密安全随机数生成器(CSPRNG)。 CSPRNGs保证,在看到之前的结果后,在计算上很难猜测下一个输出,如果生成器的状态已知,哪些值在已知输出之前。 ==真随机数生成器== 真正的随机数生成器使用物理设备或现象生成随机数,其不可预测性可以追溯到量子力学定律。 ===x86 RDSEED指令=== 较新的x86和x86-64处理器具有用于生成随机数的指令RDSEED。 要使用RDSEED,首先需要检查指令是否可用。 <source lang="asm"> mov eax, 7 ; set EAX to request function 7 mov ecx, 0 ; set ECX to request subfunction 0 cpuid shr ebx, 18 ; the result we want is in EBX... and ebx, 1 ; ...test for the flag of interest... </source> 如果EBX设置为1(或ZF未设置),则该指令可用。 然后可以使用它生成16/32/64位随机数(取决于用作参数的寄存器大小)。 如果生成了随机数,则设置进位标志。 <source lang="asm"> mov ecx, 100 ; number of retries .retry: rdseed eax jc .done ; carry flag is set on success loop .retry .fail: ; no random number available .done: ; random number is in EAX </source> 这取决于你和你的用户是否信任RDSEED。 因为它是在硬件中实现的,所以它实际上是一个黑匣子,可能包含各种错误,或者更糟的是,后门。 === 专用硬件=== 有专门用于生成“真”随机数的设备。 从消费者级TPMs到PCIe“加密加速器”都有。 这些是RDSEED/RDRAND的一种泛化,缺点是需要额外的驱动程序来与设备交互,用户可能没有安装这样的设备。 TPMs或Trusted Platform Module是可以安装在现代主板上的小型协处理器。 除了生成随机数,它们还提供[https://en.wikipedia.org/wiki/Trusted_Computing 其他可信计算服务]。 值得注意的是,Windows 11要求有TPM。 它们也可以在CPU上模拟(例如英特尔PTT或AMD fTPM)。 与RDSEED一样,后门可能是一个问题。 ===手动采样=== 在一个运行的系统中,有很多进程实际上是随机的。 想想每秒钟都会出现的各种中断:精度为百万分之几的计时器、外围I/O、用户与整个系统的交互等等。 挑战在于找到(矛盾的是)可靠随机且难以从外部影响和观察的来源。 对于这些源中的每一个,必须估计它们贡献了多少熵。 测量将各自的熵量添加到池中,而读取则会降低熵。 因为你可以完全控制这种生成方法,所以还可以合并硬件生成器生成的值。 你可以自己决定这些生成器的熵是多少,甚至是0位。 当使用计时作为熵源时,读取的时间戳应尽可能精确。 TSC在这方面做得很好。 衡量从该操作中获得的熵需要了解事件发生的时间窗口和TSC的滴答率。 例如,如果一个TSC的滴答频率为3 GHz,并且一个事件有一个10毫秒的窗口要发生,那么TSC读取可以有3000万个值中的任何一个,这意味着从中获得的熵约为24.8位。 如果TSC较慢,只有1GHz,那么熵将只有约23.2位。 ====对抗熵==== 如果对手能够以某种方式观察熵池的状态,并将自己的熵贡献给熵池,那么他们提供熵的方式可能会迫使熵池进入较低的熵状态。 一个简单的例子是,熵源定期检查池中的某个位是否已设置,然后提供熵,直到它被清除。 这样,在大多数情况下,给定的位是清晰的,熵缓冲区可能处于的状态数减少了一半。 DJB的博客上描述了一种更复杂的攻击:https://blog.cr.yp.to/20140205-entropy.html 部分原因还在于,如果用户请求一个随机数,不经修改地公开熵池是不明智的。 如果对手可以访问该池(通过专用的“添加熵”接口或采样事件源),则很容易对其下毒。 用于隐藏确切状态的常用方法是使用加密安全的哈希函数(如SHA-256),结合计数器(例如熵计数器)和salt对池(部分)进行哈希运算。 因为这些散列算法很难反转,所以它的输入不容易猜测。 只有当池中还有一些熵时,才需要这样做。 ==加密安全的伪随机数生成器(Cryptographically secure pseudorandom number generators)== 现在跟随一些CSPRNGs。 重要的是要记住,与任何加密一样,如果你打算实际使用它,最好不要自制它。 出错的方式很多,算法越复杂,出错的几率就越大。 当然,对于业余用途来说,这是完全好的;只是不要使用手工制作的TLS密钥源进行网上银行业务。 ===x86 RDRAND指令=== RDRAND比RDSEED稍早,并提供([https://software.intel.com/content/www/us/en/develop/blogs/the-difference-between-rdrand-and-rdseed.html 英特尔声称的)CSPRNG。 它的存在由CPUID leaf,ECX位30表示: <source lang="asm"> mov eax, 1 ; set EAX to request function 1 mov ecx, 0 ; set ECX to request subfunction 0 cpuid shr ecx, 30 ; the result we want is in ECX... and ecx, 1 ; ...test for the flag of interest... </source> 如果ECX设置为1(或ZF未设置),则该指令可用。用法与RDSEED相同: <source lang="asm"> mov ecx, 100 ; number of retries .retry: rdrand eax jc .done ; carry flag is set on success loop .retry .fail: ; no random number available .done: ; random number is in EAX </source> 它由RDSEED读取的同一个熵源自动播种,不能手动播种。 因此,它与RDSEED一样需要注意。 === Ciphers === 通过植入一个安全Cipher(如ChaCha20和AES)并运行多个循环来构造CSPRNG是相当常见的,在这些循环中,输出与运行的计数器一起被重新加密。 每隔一段时间,就会创建一个新密钥,可能涉及另一个安全的随机源。 [https://www.bentasker.co.uk/blog/software-development/689-writing-a-chacha20-based-csprng 编写(和植入后门)一个基于CSPRNG的ChaCha20的]可能是一篇关于这个主题的有趣文章,以及它如何以令人惊讶的方式出错。 ==伪随机数生成器== 接下来是各种常规PRNG。 它们在质量、简单性和速度的各个方面都有所不同。 仔细阅读每个解释! === 标准的示例=== 直接取自 [http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf C标准文档(第347页)]: <source lang="c"> //以下函数定义了rand和srand的可移植实现。 static unsigned long int next = 1; // 注:“unsigned long int”假定为32位宽 int rand(void) // RAND_MAX假设为32767 { next = next * 1103515245 + 12345; return (unsigned int) (next / 65536) % 32768; } void srand(unsigned int seed) { next = seed; } </source> 这是一个相当标准但平庸的标准 [https://en.wikipedia.org/wiki/Linear_congruential_generator 线性同余发生器](Linear Congruential Generator-LCG)。 它返回15个随机位。 它基于这个递推公式 X<sub>n+1</sub> = (aX<sub>n</sub> + c) mod m 其中,模数m是LCG能产生的最大随机值数。 对于C标准中的示例,a=1103515245、C=12345和m=2<sup>32</sup>(隐式)。 LCG的质量在很大程度上取决于这些值,很难找到好的值。 维基百科给出了[https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use 常见参数列表]。 ===斐波那契随机数=== 斐波那契序列的特殊“重拍”可以用来生成随机数。 这是通过有4个“种子”来实现的,这些种子一开始是非常奇怪的值(例如45、80、22、32)。 seed()函数的作用是:在序列的末尾添加一个新的种子,并删除第一个种子(种子被称为A、B、C和D)。 rand()函数只返回种子的总和,并用结果调用seed()。 [[User:Mariuszp|Glidix]]就是这种RNG实现的一个很好的例子。 ===线性反馈移位寄存器=== 线性反馈移位寄存器(LFSR)是一种简单的方法,可以在给定非零种子的情况下生成很长的随机数序列。 其思想如下:“n”位LFSR有“n”位(0或1)。 最初,寄存器用“n”位种子填充。 对于下一个要生成的值,从寄存器中“点击”某些位,并将它们异或在一起。 将结果二进制值输入最左边的位,将所有位向右移动一个位置。 从最右侧LFSR移出的位是生成的随机位。 LFSR可以产生的比特序列最终会重复,但通过仔细选择异或比特,LFSR的周期可以增加到最多2<sup>n</sup>-1比特。 这意味着在一个2<sup>n</sup>-1位的序列之后,将返回相同的序列。 然而,这是所有伪随机数生成器都具有的一个特性:在不改变种子的情况下,它们最终会重复自己。 下面是一个16位LFSR的示例,使用位11、13、14和16异或作为其输入。 该LFSR的周期为65535位,因此在序列自身重复之前,它将生成一个65535位的伪随机序列。 LFSR产生的下一个位是1(位16的值),下一个输入位是0。 <pre> 1 11 13 14 16 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ INPUT --> | 0 1 0 0 0 1 0 0 1 1 1 1 0 0 0 1 | --> OUTPUT +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ INPUT = bit 11 XOR bit 13 XOR bit 14 XOR bit 16 </pre> 更大(和更小)的LFSR也是可能的。 聪明人已经推导出多项式,确保在任何非零输入的情况下,LFSR的周期尽可能大(2<sup>n</sup>-1)。 这样的多项式被写成x<sup>16</sup>+x<sup>14</sup>+x<sup>13</sup>+x<sup>11</sup>+1。 对于这个“n”=16的多项式示例,位16、14、13和11必须异或在一起,并作为输入提供,从左开始计数,从1开始。 Polynomials for other values of ''n'' can be found [http://en.wikipedia.org/wiki/Linear_feedback_shift_register#Some_polynomials_for_maximal_LFSRs here on Wikipedia] and on page 5 of [http://www.xilinx.com/support/documentation/application_notes/xapp052.pdf this PDF document]. 请注意,种子永远不能为零。 这也意味着,不可能所有寄存器的位值都为零,而在2个寄存器的可能组合中,不允许全零状态。 因此,2个<sup>n</sup>-1状态是可能的最大值。 === Wichmann-Hill === 1982年,Brian Wichmann和David Hill[https://doi.org/10.2307/2347988 建议]组合三个线性同余生成器,然后对结果进行归一化和求和,得到一个均匀分布在0和1之间的数。 一个常见的例子是: <source lang="c"> static uint16_t seed[3]; /* seed with numbers between 1 and 30000 */ float wichmann_hill(void) { seed[0] = (171 * seed[0]) % 30269; seed[1] = (172 * seed[1]) % 30307; seed[2] = (170 * seed[2]) % 30323; return fmod(seed[0] / 30269.0 + seed[1] / 30307.0 + seed[2] / 30323.0, 1.0); } </source> 已知该版本的周期略小于7万亿(最常见的倍数为30268、30306和30322)。 === Mersenne Twister === Mersenne Twister是一款非常受欢迎的PRNG,可在大多数平台上使用;例如,它是C++11标准库所需的生成器集的一部分。 64位MT-19937版本的C实现如下: 根据维基百科实现: <source lang="c"> #include <stddef.h> #include <stdint.h> #include <assert.h> #ifdef USE32 typedef uint32_t word_t; #define STATE_SIZE 624 #define MIDDLE 397 #define INIT_SHIFT 30 #define INIT_FACT 1812433253 #define TWIST_MASK 0x9908b0df #define SHIFT1 11 #define MASK1 0xffffffff #define SHIFT2 7 #define MASK2 0x9d2c5680 #define SHIFT3 15 #define MASK3 0xefc60000 #define SHIFT4 18 #else typedef uint64_t word_t; #define STATE_SIZE 312 #define MIDDLE 156 #define INIT_SHIFT 62 #define TWIST_MASK 0xb5026f5aa96619e9 #define INIT_FACT 6364136223846793005 #define SHIFT1 29 #define MASK1 0x5555555555555555 #define SHIFT2 17 #define MASK2 0x71d67fffeda60000 #define SHIFT3 37 #define MASK3 0xfff7eee000000000 #define SHIFT4 43 #endif #define LOWER_MASK 0x7fffffff #define UPPER_MASK (~(word_t)LOWER_MASK) static word_t state[STATE_SIZE]; static size_t index = STATE_SIZE + 1; static void seed(word_t s) { index = STATE_SIZE; state[0] = s; for (size_t i = 1; i < STATE_SIZE; i++) state[i] = (INIT_FACT * (state[i - 1] ^ (state[i - 1] >> INIT_SHIFT))) + i; } static void twist(void) { for (size_t i = 0; i < STATE_SIZE; i++) { word_t x = (state[i] & UPPER_MASK) | (state[(i + 1) % STATE_SIZE] & LOWER_MASK); x = (x >> 1) ^ (x & 1? TWIST_MASK : 0); state[i] = state[(i + MIDDLE) % STATE_SIZE] ^ x; } index = 0; } static word_t mt_random(void) { if (index >= STATE_SIZE) { assert(index == STATE_SIZE || !"Generator never seeded"); twist(); } word_t y = state[index]; y ^= (y >> SHIFT1) & MASK1; y ^= (y << SHIFT2) & MASK2; y ^= (y << SHIFT3) & MASK3; y ^= y >> SHIFT4; index++; return y; }</source> 有些实现会自动使用seed 5489为生成器设定种子,但这(显然)会导致每次初始化时都有相同的输出。 它的表现优于上面列出的所有PRNG,但由于其庞大的状态规模,速度相当缓慢。 它在一些统计领域也存在问题。 参考 [https://arxiv.org/abs/1910.06437 "It is high time we let go of the Mersenne Twister"]。 ===PCG系列=== [https://www.pcg-random.org/ PCG系列]是PRNG领域的一个相对较新的成员。 最简单的版本使用带有置换运算符的LCG对输出进行置乱。 它是为了通过尽可能多的统计测试而建造的,同时仍然保持小而快。 There is a [https://www.pcg-random.org/pdf/hmc-cs-2014-0905.pdf comprehensive paper] describing the generators and Apache 2.0-licensed code is available: https://www.pcg-random.org/download.html. 请注意,该网站声称,PCG的输出比其他PRNG的输出更难预测,这意味着PCG更安全。 It is possible to predict some generators after [https://pcg.di.unimi.it/pcg.php#claims only three outputs], so it should not be considered "hard to break" and definitely not "more secure". “非”加密安全PRNG的可预测性通常不是问题。 === xoshiro系列=== 和PCG一样,xoshiro和相关的xoroshiro家族也是相当新的PRNG。 === 搞一个自创的 === 如果你仍然不满意,你可能想建立自己的PRNG。 你可以通过编写一些看起来不错的东西,并将其置于自动化的统计测试套件(如[http://simul.iro.umontreal.ca/testu01/tu01.html TestU01]的SmallCrush中,依次Crush,BigCrush。 这些测试非常容易运行,可以快速指出问题,例如: <source lang="c"> #include <unif01.h> #include <bbattery.h> #include <stdint.h> static uint32_t next = 1; unsigned int custom_rand(void) { // This is a terrible generator! next = (next * 0x4B4B9656U) ^ (next * 0x565AC3C3U) + 1; return next * (next - 1); } int main(void) { unif01_Gen *gen = unif01_CreateExternGenBits("custom_rand", custom_rand); bbattery_SmallCrush(gen); //或者bbattery_Crush,bbattery_BigCrush unif01_DeleteExternGenBits(gen); } </source> SmallCrush将报告该生成器在15次统计测试中有12次失败。 因此,也不需要进行其他速度慢得多的测试。 [[Category:Common Algorithms]]
返回至“
Random Number Generator
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息