我想安全地生成[0,N]范围内的随机数,其中N是参数。但是,System.Security.Cryptography.RandomNumberGenerator仅提供GetBytes()方法以使用随机值填充数组。
(我需要在稍微修改过的SRP版本中使用的随机整数。“略微修改”的部分是我无法控制的,这也是我接触加密内容的唯一原因。)
我已经写了一个方法来做到这一点,但我正在寻找一种更好的方法,或者至少确认我做得对。
using System.Numerics
///<summary>Generates a uniformly random integer in the range [0, bound).</summary>
public static BigInteger RandomIntegerBelow(this System.Security.Cryptography.RandomNumberGenerator source, BigInteger bound) {
Contract.Requires<ArgumentException>(source != null);
Contract.Requires<ArgumentException>(bound > 0);
Contract.Ensures(Contract.Result<BigInteger>() >= 0);
Contract.Ensures(Contract.Result<BigInteger>() < bound);
//Get a byte buffer capable of holding any value below the bound
var buffer = (bound << 16).ToByteArray(); // << 16 adds two bytes, which decrease the chance of a retry later on
//Compute where the last partial fragment starts, in order to retry if we end up in it
var generatedValueBound = BigInteger.One << (buffer.Length * 8 - 1); //-1 accounts for the sign bit
Contract.Assert(generatedValueBound >= bound);
var validityBound = generatedValueBound - generatedValueBound % bound;
Contract.Assert(validityBound >= bound);
while (true) {
//generate a uniformly random value in [0, 2^(buffer.Length * 8 - 1))
source.GetBytes(buffer);
buffer[buffer.Length - 1] &= 0x7F; //force sign bit to positive
var r = new BigInteger(buffer);
//return unless in the partial fragment
if (r >= validityBound) continue;
return r % bound;
}
}
答案 0 :(得分:1)
该实现对于在范围内生成无偏的整数看起来是正确的。
另一种方法是将随机字节作为二进制分数0.xxxxxx...
中的无限数字,乘以bound
并向下舍入。它实际上不会占用无限数字,只需要确定舍入结果的数量。我认为这更复杂。
但是,您的协议可能不需要在整个范围内生成数字。 RFC 5054部分3.1仅需要256位私有指数。该数字来自哈希输出的比特长度加倍,并且需要使用安全素数。该RFC的早期草案提到了On Diffie-Hellman key agreement with short exponents的解释,该解释位于第4节的第一段。
如果您更喜欢更多随机位,请取range
减去1的位长。这就是OpenSSL does for Diffie-Hellman(搜索BN_rand
及其之前的行)。这仍然更容易生成,并且比整个范围短两倍,如果这有所不同,你应该使用更大的素数。
答案 1 :(得分:1)
您的代码看起来正确且无偏见。但是,如果您在演奏后,可能需要稍微更改一下,具体取决于您使用的随机源的速度。我们的想法是掩盖更多位,以便随机值r
小于2*bound
。如果绑定的位是x
(长度除了符号位),那么生成n = ((x+8)/8)
字节的缓冲区和掩码超出(n*8-x)
位。在C#中,这应该是:
var x = BitLength(bound);
var n = ((x + 8) / 8);
var buffer = new Byte[n];
var mask = 0xFF >> (8 * n - x);
while (true) {
source.GetBytes(buffer);
buffer[n - 1] &= mask;
var r = new BigInteger(buffer);
if (r < bound)
return r;
}
使用这种代码,您可能需要从源中询问更多随机字节,但您可以避免模块化缩减(%
运算符)。一个合适的PRNG应该比大整数的分割快得多,所以这应该是一个更好的权衡 - 但这实际上取决于随机源的性能,并且,因为它是一个性能问题,它不能完全没有尝试回答。有可能的是,作为整体SRP实施的一部分,它无论如何都不会产生明显的差异。
我上面使用了BitLength()
函数,它似乎不存在于C#中(Java的BigInteger
类有一个bitLength()
方法,但显然Microsoft忘了在其大整数中包含一个实现 - 这是一种耻辱,因为它似乎实际上包含一个名为_bits
的私有字段来维护该值。可以从bound
值的表示(字节)有效地计算比特长度。因此,代码将变为这样:
var buffer = bound.ToByteArray();
var n = buffer.length;
var msb = buffer[n - 1];
var mask = 0;
while (mask < msb)
mask = (mask << 1) + 1;
while (true) {
source.GetBytes(buffer);
buffer[n - 1] &= mask;
var r = new BigInteger(buffer);
if (r < bound)
return r;
}
我使用的事实是bound
返回的ToByteArray()
编码的长度(以字节为单位)正是我需要的n
的值。