Безопасное создание равномерно случайного BigInteger

Я хочу безопасно генерировать случайное число в диапазоне [0, N), где N-параметр. Однако Система.Безопасность.Криптография.RandomNumberGenerator предоставляет только метод GetBytes () для заполнения массива случайными значениями.

(Мне нужны случайные целые числа для nonces, используемые в слегка измененной версии 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 ответов


ваш код выглядит правильным и беспристрастным. Однако вы можете немного изменить его, если вы после производительности, и в зависимости от скорости случайного источника вы используете. Идея состоит в том, чтобы замаскировать еще несколько битов, чтобы случайное значение r меньше, чем 2*bound. Если длина в битах of bound is 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 что мне нужно.


эта реализация выглядит правильной для генерации непредвзятых целых чисел в диапазоне.

другим подходом было бы взять случайные байты как бесконечные цифры в двоичной дроби 0.xxxxxx..., умножьте это на bound и округлить. На самом деле это не займет бесконечные цифры, столько, сколько нужно, чтобы определить, что такое результат округления. Я думаю, что это сложнее.

но генерация чисел в полном диапазоне может быть ненужной для вашего протокола. RFC 5054 раздел 3.1 требует только 256-битных частных показателей. Это число происходит от удвоения битовой длины хэш-вывода и требует использования безопасного простого числа. Более ранний проект этого RFC ссылался на о ключевом соглашении Диффи-Хеллмана с короткими экспонентами для объяснения, которое находится в первом абзаце раздела 4 там.

если вы предпочитаете более случайные биты, возьмите длину бита range минус 1. Это то, что OpenSSL для Диффи-Хеллман!--10--> (поиск BN_rand и строка перед ним). Это все еще легче генерировать и в два раза меньше, чем полный диапазон, и если это имеет значение, вы должны использовать большее простое число.