Random.choice не случайный

Я использую Python 2.5 для Linux в нескольких параллельных процессах FCGI. Я использую

    chars = string.ascii_letters + string.digits
    cookie = ''.join([random.choice(chars) for x in range(32)])

для создания отдельных файлов cookie. Предполагая, что RNG высевается из /dev/urandom, и что последовательность случайных чисел поступает от twister Мерсенны, я бы ожидал, что вероятность столкновения практически равна нулю.

Тем не менее, я вижу регулярные коллизии, хотя в любое время регистрируются только несколько (< 100) пользователей.

Почему случайные числа не являются более случайными?

Ответы

Ответ 1

Он не должен генерировать дубликаты.

import random
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
def gen():
    return ''.join([random.choice(chars) for x in range(32)])

test = [gen() for i in range(100000)]
print len(test), len(set(test)) # 100000 100000

Шансы дублирования значительны с символами = "ab"; 126 дубликатов в 1000000 итерациях. Это не имеет значения с 62.

Тем не менее, это не является хорошим способом создания куки файлов, поскольку файлы cookie сеанса должны быть непредсказуемыми, чтобы избежать атак, связанных с кражей файлов cookie других пользователей. Mersenne Twister не предназначен для создания безопасных случайных чисел. Это то, что я делаю:

import os, hashlib
def gen():
    return hashlib.sha1(os.urandom(512)).hexdigest()

test = [gen() for i in range(100000)]
print len(test), len(set(test))

..., который должен быть очень безопасным (т.е. трудно взять строку cookie сеанса и угадать другие существующие файлы cookie сеанса из них).

Ответ 2

Это не обычный сценарий столкновения:

  • 32 символа с 62 опциями на символ эквивалентны 190 бит (log2 (62) * 32)
  • В соответствии с парадоксом дня рождения вы должны получать столкновение естественным образом каждые 2 ** 95 куки, что означает никогда

Может ли это быть проблема concurrency?

  • Если это так, используйте разные экземпляры random.Random для каждого потока
  • Можно сохранить эти экземпляры в локальном хранилище потоков (threading.local())
  • В linux Python должен засеять их с помощью os.urandom() - не системного времени, поэтому вы должны получать разные потоки для каждого потока.

Ответ 3

  • Я не знаю, как генерируются ваши процессы FCGI, но возможно ли, что он использует fork() после того, как интерпретатор Python запущен (и случайный модуль был импортирован чем-то), следовательно, эффективно посев два процесса random._inst из того же источника?

  • Может быть, отлаживается некоторая отладка, чтобы проверить, правильно ли она посеяна из урандома, и не отступать к менее строгим временным затратам?

eta re комментарий: мужчина! То, что я тогда задохнулся; если в процессе запуска RNG всегда имеет другое состояние, я не вижу, как вы могли столкнуться. Weird. Должно было бы внести много государственных протоколов для изучения конкретных случаев, которые приводят к столкновениям, я думаю, что звучит как много работы по трафику через журналы. Может ли быть (1a), что FCGI-сервер обычно не развивает, но иногда (возможно, под нагрузкой или что-то еще)?

Или (3) некоторые проблемы более высокого уровня, такие как сломанный прокси-сервер HTTP, передающий одно и то же Set-Cookie нескольким клиентам?

Ответ 4

Мне пришлось стереть свой первоначальный ответ, который предположил, что генератор не высевается из /dev/urandom, так как его источник (для Python 3.x) ясно говорит, что это:

def seed(self, a=None):
    """Initialize internal state from hashable object.

    None or no argument seeds from current time or from an operating
    system specific randomness source if available.

    If a is not None or an int or long, hash(a) is used instead.
    """

    if a is None:
        try:
            a = int(_hexlify(_urandom(16)), 16)
        except NotImplementedError:
            import time
            a = int(time.time() * 256) # use fractional seconds

    super().seed(a)
    self.gauss_next = None

Поэтому я смиренно признаю, что в мире есть тайны, которые я не могу расшифровать.

Ответ 5

Чтобы избежать этой проблемы, вы можете использовать последовательность файлов cookie, которые гарантированно будут отличаться (вы можете, например, использовать набор). Каждый раз, когда вы даете куки файл кому-то, вы берете его из последовательности и добавляете к нему еще одну. Другой вариант - создать UUID и использовать его в качестве файла cookie.

Другим способом избежать проблемы может быть удерживание закрытого ключа и использование контрольной суммы (например, MD5) закрытого ключа со связанным с ней значением счетчика. Тогда вероятность столкновения будет очень низкой. Чтобы быть более безопасным, добавьте еще несколько переменных в контрольную сумму, например текущее время, ip-адрес пользователя,...

Библиотеки для создания файлов cookie существуют. Любая реализация WSGI, вероятно, содержит генератор файлов cookie.

Если вас интересует только случайность ваших строк, вы можете сгенерировать файл с, скажем, одним миллионом файлов cookie и выполнить проверку случайности в этом файле. Это, однако, не то, что я бы рекомендовал.