【问题标题】:Securely generating a uniformly random BigInteger安全地生成一个均匀随机的 BigInteger
【发布时间】:2011-03-16 19:43:37
【问题描述】:

我想在 [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;
    }
}

【问题讨论】:

    标签: .net security random cryptography biginteger


    【解决方案1】:

    您的代码看起来正确且公正。但是,如果您追求性能,并且取决于您使用的随机源的速度,您可能需要对其进行一些更改。这个想法是屏蔽掉更多的位,以便随机值r 小于2*bound。如果bound的长度位是x(长度不包括符号位),那么你生成一个n = ((x+8)/8)字节的缓冲区,并屏蔽掉上面的@ 987654325@ 位。在 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() 方法,但显然微软忘记在他们的大整数实现中包含一个 - 这是一个耻辱,因为似乎该实现确实包含一个名为 _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 的值。

    【讨论】:

    • 我也很惊讶没有 BitLength 成员。我并不特别担心 Mod 操作的成本,因为无论如何我都必须对结果值使用 ModPow。
    【解决方案2】:

    该实现对于在一个范围内生成无偏整数看起来是正确的。

    另一种方法是将随机字节作为二进制分数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 及其前面的行)。这仍然更容易生成,并且比完整范围短不到两倍,如果这有所不同,您应该使用更大的素数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-24
      • 2011-08-15
      • 2013-05-12
      • 1970-01-01
      • 2020-09-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多