(Безопасная) случайная строка?



В Lua обычно генерируются случайные значения и / или строки с помощью math.random & math.randomseed, где os.time используется для math.randomseed.



Однако у этого метода есть один существенный недостаток: возвращаемое число всегда так же случайно, как и текущее время, и интервал для каждого случайного числа составляет одну секунду, что слишком долго, если требуется много случайных значений за очень короткое время.

Эта проблема даже отмечена пользователями Lua wiki: http://lua-users.org/wiki/MathLibraryTutorial , и соответствующие случайные струны: http://lua-users.org/wiki/RandomStrings .



Поэтому я сел и написал другой алгоритм (если его вообще можно так назвать), который генерирует случайные числа (mis -), используя адреса памяти таблиц:

math.randomseed(os.time())
function realrandom(maxlen)
local tbl = {}
local num = tonumber(string.sub(tostring(tbl), 8))
if maxlen ~= nil then
num = num % maxlen
end
return num
end

function string.random(length,pattern)
local length = length or 11
local pattern = pattern or '%a%d'
local rand = ""
local allchars = ""
for loop=0, 255 do
allchars = allchars .. string.char(loop)
end
local str=string.gsub(allchars, '[^'..pattern..']','')
while string.len(rand) ~= length do
local randidx = realrandom(string.len(str))
local randbyte = string.byte(str, randidx)
rand = rand .. string.char(randbyte)
end

return rand
end


Поначалу все кажется совершенно случайным, и я уверен, что так оно и есть... по крайней мере, для текущей программы.

Итак, мой вопрос в том, насколько случайны эти числа, возвращаемые realrandom действительно?



Или есть еще лучший способ генерировать случайные числа за более короткий промежуток времени, чем одна секунда (что подразумевает, что os.time не следует использовать, как объяснено выше), не полагаясь на внешние библиотеки, и, если это возможно, полностью кроссплатформенным способом?



Правка:

Похоже, существует серьезное недопонимание относительно способа посева ГСЧ; в производственном коде вызов math.randomseed() происходит только один раз, это было просто неудачно подобранный пример.



То, что я подразумеваю под случайная величина является случайной только один раз в секунду , легко демонстрируется этой пастой: http://codepad.org/4cDsTpcD





Поскольку этот вопрос будет понижен независимо от моих правок, я также отменил свой ранее принятый ответ - в надежде на лучшее, даже если просто лучшие мнения. Я понимаю, что вопросы, касающиеся случайных величин / чисел, обсуждались много раз раньше, но я этого не делал. нашел такой вопрос, который может иметь отношение к Луа-пожалуйста, имейте это в виду!
541   4  
lua

4 ответов:

Некоторые мысли по первой части вашего вопроса:

Итак, мой вопрос заключается в том, насколько случайны эти числа, возвращаемые realrandomна самом деле?

Ваша функция пытается обнаружить адрес таблицы, используя причуду ее реализации по умолчанию tostring(). Я не верю, что строка, возвращаемая tostring{}, имеет определенный формат или что значение, включенное в эту строку, имеет какой-либо документально подтвержденный смысл. На практике он выводится из адреса что-то связанное с конкретной таблицей, и поэтому различные таблицы преобразуются в различные строки. Однако следующая версия Lua может изменить это на что угодно, что удобно. Хуже того, формат, который он принимает, будет сильно зависеть от платформы, потому что он, по-видимому, использует спецификатор формата %p для sprintf(), который указан только как разумное представление указателя.

Есть также гораздо более серьезная проблема. В то время как адрес N-й таблицы, созданной в процессе, может кажитесь случайным на вашей платформе, tt может быть совсем не случайным. Или она может изменяться всего в нескольких битах. Например, на моей коробке win7 только несколько битов меняются, и не очень случайно:

C:...>for /L %i in (1,1,20) do @ lua -e "print{}"
table: 0042E5D8
table: 0061E5D8
table: 0024E5D8
table: 0049E5D8
table: 0042E5D8
table: 0042E5D8
table: 0042E5D8
table: 0064E5D8
table: 0042E5D8
table: 002FE5D8
table: 0042E5D8
table: 0049E5D8
table: 0042E5D8
table: 0042E5D8
table: 0042E5D8
table: 0024E5D8
table: 0042E5D8
table: 0042E5D8
table: 0061E5D8
table: 0042E5D8

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

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

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

Функция stock tostring() поставляется базовой библиотекой и реализуется функцией luaB_tostring(). Релевантным битом является следующий фрагмент:

switch (lua_type(L, 1)) {
  ...
  default:
    lua_pushfstring(L, "%s: %p", luaL_typename(L, 1), lua_topointer(L, 1));
    break;

Если вы действительно вызываете эту функцию, то конец строки будет адресом, представленным стандартным форматом C sprintf() %p, сильно связанным с конкретной таблицей. Один наблюдение состоит в том, что я видел несколько различных реализаций для %p. Windows MSVCR80.DLL (версия библиотеки C, используемая в текущем выпуске Lua для Windows) делает ее эквивалентной %08X. Моя коробка кармической коалы Ubuntu, похоже, делает ее эквивалентной %#x, которая заметно опускает ведущие нули. Если вы собираетесь разобрать эту часть строки, то вам следует сделать это более гибким способом перед лицом изменения значения %p.

Заметьте также, что делание что-нибудь подобное в библиотечном коде может преподнести вам пару сюрпризов.

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

Во-вторых, к моменту загрузки вашего модуля, какой-либо другой модуль или пользовательский код мог бы заменить запас tostring() чем-то другим. Если замена является доброкачественной (например, оболочка memoization), то она, вероятно, не имеет значения для кода, как он написан. Тем не менее, это будет источником атаки, и полностью вне контроля вашего модуля. Это не кажется мне хорошей идеей, если целью является какая-то улучшенная безопасность для вашего случайного семенного материала.

В-третьих, вы можете вообще не загружаться в стоковый интерпретатор Lua, и более крупное приложение (Lightroom, WoW, Wireshark, ...) могут заменить базовые библиотечные функции своими собственными реализациями. Это гораздо менее вероятная проблема для tostring(), но обратите внимание, что базовая библиотека print() является частой целью для замены или удаления в альтернативных реализациях, и есть модули (Lua-дорожки, например), которые ломаются, если print не является реализацией в базовой библиотеке.

  1. Вы не должны вызывать seed каждый раз, когда вы вызываете random, вы должны вызвать его только один раз, при инициализации программы (если вы не получаете seed откуда-то, например, для воспроизведения некоторого предыдущего "случайного" поведения).

  2. Стандартный генератор случайных чисел Lua имеет низкое качество в статистическом смысле (как, собственно, и стандартный генератор случайных чисел C), не используйте его, если вы заботитесь об этом. Используйте, например, модуль lrandom (доступный в LuaRocks).

  3. Если вам нужен более безопасный random, прочитайте из /dev/random на Linux. (Я думаю, что Windows должна иметь что - то в том же духе-но вам, возможно, потребуется что-то закодировать в C, чтобы использовать его.)

  4. Полагаться на значения указателей таблиц-плохая идея. Подумайте об альтернативных реализациях Lua, например, в Java - неизвестно, что они вернут. (Кроме того, значения указателей могут быть предсказуемыми, и при определенных обстоятельствах они могут быть одинаковыми каждый раз программа вызывается.)

  5. Если вы хотите получить более высокую точность для затравки (а вы захотите этого только в том случае, если Вы запускаете программу чаще, чем один раз в секунду), вы должны использовать таймер с лучшим разрешением. Например, socket.gettime() из LuaSocket. Умножьте его на некоторое значение, так как math.randomseed работает только с целой частью, а socket.gettime() возвращает время в секундах (с плавающей запятой).

    require 'socket'
    
    math.randomseed(socket.gettime() * 1e6)
    
    for i = 1, 1e3 do
      print(math.random())
    end
    

Этот метод, однако, имеет один основной слабость; возвращенное число всегда так же случайно, как и текущий время и интервал для каждого случайного число-одна секунда, что тоже очень важно. долго, если нужно много случайных величин за очень короткое время.

У него есть эти недостатки, только если вы неправильно его реализуете.

math.randomseed предполагается, что вызывается экономно-обычно только один раз в начале вашей программы, и это обычно семена с использованием os.time. Когда-то ... семя задано, вы можете использовать math.random много раз, и это даст случайные значения.

Смотрите, что происходит на этом примере:

> math.randomseed(1)
> return math.random(), math.random(), math.random()
0.84018771715471    0.39438292681909    0.78309922375861
> math.randomseed(2)
> return math.random(), math.random(), math.random()
0.70097636929759    0.80967634907443    0.088795455214007
> math.randomseed(1)
> return math.random(), math.random(), math.random()
0.84018771715471    0.39438292681909    0.78309922375861
Когда я меняю семя с 1 на 2, я получаю разные случайные результаты. Но когда я возвращаюсь к 1," случайная последовательность " сбрасывается. Я получаю те же значения, что и раньше.

os.time() возвращает постоянно увеличивающееся число. Использование его в качестве семени уместно; тогда вы можете вызывать math.random вечно и иметь разные случайные числа каждый раз, когда вы вызываете его.

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

Другими словами:

  • вызовите математику.случайное семя с подходящим семенем (os.time() нормально в 99% случаев) в начале вашей программы
  • вызывайте math.random каждый раз, когда вам нужно случайное число.

С уважением!

На ум приходит несколько важных вещей:

  • в большинстве других языков вы обычно вызываете случайную функцию 'seed' только один раз в начале программы или, возможно, в ограниченное время на протяжении всего ее выполнения. Как правило, вы не хотите вызывать его каждый раз, когда генерируете случайное число / последовательность. Если вы вызовете его один раз, когда программа запускается, вы обойдете ограничение "один раз в секунду". Вызывая его каждый раз, вы можете на самом деле закончить с меньшим количеством случайности в вашем результаты.
  • ваша функция realrandom (), похоже, полагается на частную деталь реализации Lua. Что произойдет в следующем крупном выпуске, если эта деталь изменится, чтобы всегда возвращать одно и то же число или только четные числа и т. д.... Просто потому, что он работает на данный момент не является достаточно сильной гарантией, особенно в случае, когда требуется безопасный ГСЧ.
  • Когда вы говорите: "все кажется совершенно случайным", как вы оцениваете эту производительность? Мы, люди, ужасно умеем определять, есть ли последовательность. случайность или нет, и просто глядя на последовательность чисел было бы практически невозможно сказать, были ли они случайными или нет. Существует множество способов количественной оценки "случайности" ряда, включая частотное распределение, автокорреляцию, сжатие и многое другое, выходящее далеко за пределы моего понимания.
  • Если вы пишете настоящий "безопасный PRNG" для производства, не пишите свой собственный! Исследуйте и используйте библиотеку или алгоритм экспертами, которые потратили годы / десятилетия на изучение, проектирование и попытки разбить его. Истинная безопасная генерация случайных чисел трудна.

Если вам нужна дополнительная информация, начните со статьиPRNG в Википедии и используйте ссылки/ссылки там по мере необходимости.

Comments

    Ничего не найдено.