安全地生成一个随机的BigInteger

时间:2011-03-16 19:43:38

标签: .net security random cryptography biginteger

我想安全地生成[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;
    }
}

2 个答案:

答案 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的值。