Генерация одноразового номера с количеством тиков DateTime

#c# #nonce

#c# #одноразовый номер

Вопрос:

В нашем веб-приложении мы используем внешнюю службу для определенной функции. Чтобы запросить эту внешнюю службу, мы должны добавить key в заголовок запроса, который является целым числом и уникален для всех моих запросов, технически вызываемых nonce

для генерации одноразового номера я использовал

 var nonce = (long) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).Ticks*100   random.Next(100);
  

Теперь при 100 одновременных запросах ключ дублируется. Как дублируется ключ?

Я не могу использовать GUID, потому что мне нужно постоянно увеличивающееся целое значение.

Комментарии:

1. Время не является хорошим источником энтропии для одноразовых сообщений. Почему вы не используете GUID ?

2. Если у вас действительно параллельные запросы, то DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).Ticks*100 является константой для этих запросов. Тогда у вас осталось всего random.Next(100) . Тогда для возникновения коллизии не потребуется много времени. Тривиальным (но не идеальным) вариантом было бы просто выполнить random.Next() .

3. @ArthurAttout Мне нужно постоянно увеличивающееся целочисленное значение.

4. Зачем вам нужно, чтобы оно увеличивалось? В зависимости от ваших требований (и естественного значения слова одноразовый номер ) число должно быть уникальным. Для этого будут очень полезны идентификаторы GUID (они не будут уникальными, но вероятность возникновения коллизии крайне, крайне мала)

5. (кстати, ваш исходный код также не гарантирует, что ваше число будет постоянно увеличиваться)

Ответ №1:

Если у вас действительно параллельные запросы, то DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).Ticks * 100 является константой для этих запросов. Тогда у вас осталось всего random.Next(100) , и тогда для возникновения коллизии не потребуется много времени. Тривиальным (но не идеальным) вариантом было бы просто выполнить random.Next() .

Лучшей идеей было бы следующее:

 [ThreadStatic]
private static Random __random = new Random();
private static int shift = 32;
private static long counter = 0L;

public long GenerateNextNonce()
{
    var major =   counter << shift;
    var minor = (DateTime.UtcNow.Ticks ^ __random.Next()) amp; (1L << shift - 1);
    return major   minor;
}
  

counter Гарантирует, что у вас будет возрастающая последовательность чисел — одного этого было бы достаточно для создания одноразового номера, но это очень предсказуемо, поэтому было бы открыто для взлома.

Вычисления DateTime.UtcNow.Ticks ^ __random.Next() обеспечивают получение довольно случайного числа, которое не зависит исключительно от реализации Random , поэтому оно гарантирует, что это число будет в высшей степени непредсказуемым, но оно не обязательно увеличивается.

Использование shift значения гарантирует, что counter значение будет перенесено на «старшую» или основную часть числа. Вызов (DateTime.UtcNow.Ticks ^ __random.Next()) amp; (1L << shift - 1) усекает старшие биты случайной части одноразового номера, гарантируя, что младшее значение не разделяет никакие биты с основным числом.

Я запустил это с shift значением 32 и выдал 100_000_000 значения и исчерпал только менее 5% доступных чисел, направляющихся к long.MaxValue . Пока вы производите менее 2 миллиардов одноразовых сообщений, у вас все должно быть хорошо. Если вы хотите больше, уменьшите shift .

Ответ №2:

Я думаю, проблема в том, что random.Next(100) .

Согласно документации, случайным образом.Далее возвращается неотрицательное значение, которое меньше 100. После 100 одновременных запросов этот метод может возвращать одно и то же значение.

Как предлагалось в комментариях выше, вам следует использовать идентификаторы GUID, поскольку вероятность того, что они будут одинаковыми, крайне мала.

Или же, если вам не нужны идентификаторы GUID, вы можете попробовать приведенный ниже код (взятый из этого блога).

Это должно помочь вам решить проблему.

 public static string GetNonce()
{
    // better to get unique random number if 
    // called mulitple times
    Random r = RandomProvider.GetThreadRandom(); 

    DateTime created = DateTime.Now;

    string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(SHA1Encrypt(created   r.Next().ToString())));

    return nonce;
}
  

Надеюсь, это поможет.

Комментарии:

1. «Идентификаторы GUID, поскольку они всегда будут уникальными» — ну, лучшее, что вы могли бы сказать, это то, что вероятность того, что они будут одинаковыми, крайне мала.

2. Спасибо @Enigmativity ! Я думал о том же, когда набирал ответ. Но изначально этого избегал, поскольку я не был уверен, требуется ли эта деталь в контексте вопроса. Но в любом случае, я добавил это в ответ сейчас.

3. @Moshii — вы пробовали метод GetNonce из ответа? Работает ли это для уровня параллелизма, ожидаемого вашей программой?

4. @ManojChoudhari Как я уже говорил, из моего веб-приложения я делаю http-запрос к другой службе, и api принимает только целочисленное значение.

5. @Moshii — можете ли вы указать, какова область действия «случайной» переменной? Создается ли он каждый раз, когда вы хотите сгенерировать одноразовый номер, для одного и того же приложения?