Разница между java.util.Random и java.security.SecureRandom

Моя команда получила серверный код (на Java), который генерирует случайные токены, и у меня есть вопрос относительно этого -

Назначение этих токенов довольно чувствительно - используется для идентификатора сеанса, ссылок для сброса пароля и т.д. Поэтому они должны быть криптографически случайными, чтобы не допустить, чтобы кто-то их угадал или применил грубую силу. Маркер является "длинным", поэтому он имеет длину 64 бита.

В настоящее время код использует класс java.util.Random для генерации этих токенов. документация для java.util.Random четко гласит следующее:

Экземпляры java.util.Random не являются криптографически безопасными. Вместо этого рассмотрите возможность использования SecureRandom для получения криптографически безопасного генератора псевдослучайных чисел для использования чувствительными к безопасности приложениями.

Однако способ, которым код в настоящее время использует java.util.Random, заключается в следующем: он создает экземпляр класса java.security.SecureRandom, а затем использует метод SecureRandom.nextLong() для получения начального числа, которое используется для создания экземпляра класса java.util.Random. Затем он использует метод java.util.Random.nextLong() для генерации токена.

Итак, мой вопрос сейчас - все еще небезопасно, учитывая, что java.util.Random высевается с использованием java.security.SecureRandom? Нужно ли изменять код так, чтобы он использовал java.security.SecureRandom исключительно для генерации токенов?

В настоящее время код запускает Random один раз при запуске

Ответ 1

Стандартная реализация Oracle JDK 7 использует то, что называется линейным конгруэнтным генератором для создания случайных значений в java.util.Random.

Взято из java.util.Random исходного кода (JDK 7u2) из ​​комментария к методу protected int next(int bits), который является тем, который генерирует случайные значения:

Это линейный конгруэнтный генератор псевдослучайных чисел, так как   определяемый Д. Х. Лемером и описанный Дональдом Э. Кнутом в    Искусство программирования, Том 3:    Семинумерные алгоритмы, раздел 3.2.1.

Предсказуемость линейных конгруэнтных генераторов

Уго Krawczyk написал довольно хорошую статью о том, как можно предсказать эти LCG ( "Как предсказать конгруэнтные генераторы" ). Если вам повезло и интересно, вы все равно можете найти бесплатную загружаемую версию в Интернете. И еще много исследований, которые ясно показывают, что вы должны никогда использовать LCG для критически важных для безопасности целей. Это также означает, что ваши случайные числа предсказуемы прямо сейчас, что вам не нужно для идентификаторов сеансов и т.п.

Как разбить линейный конгруэнтный генератор

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

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

Злоумышленник просто вычислит семя из наблюдаемых выходных значений. Это занимает значительно меньше времени, чем 2 ^ 48 в случае java.util.Random. Неверующие могут опробовать этот эксперимент, где показано, что вы можете прогнозировать будущие выходы Random, наблюдая только два (!) Выходных значения во времени примерно 2 ^ 16. На современном компьютере не требуется даже секунды, чтобы предсказать вывод ваших случайных чисел прямо сейчас.

Заключение

Замените текущий код. Используйте SecureRandom исключительно. Тогда, по крайней мере, у вас будет небольшая гарантия того, что результат будет трудно предсказать. Если вам нужны свойства криптографически безопасного PRNG (в вашем случае, что вы хотите), вам нужно идти только с SecureRandom. Быть умным в изменении способа, которым он должен был использоваться, почти всегда приводит к чему-то менее безопасному...

Ответ 2

Случайное имеет только 48 бит, где SecureRandom может иметь до 128 бит. Таким образом, шансы повторения в securerandom очень малы.

Случайный использует system clock как семя/или для генерации семени. Таким образом, они могут быть легко воспроизведены, если злоумышленник знает время, в которое было произведено семя. Но SecureRandom принимает Random Data из вашего os (они могут быть интервалом между нажатиями клавиш и т.д. - большинство os собирают эти данные, храня их в файлах - /dev/random and /dev/urandom in case of linux/solaris), и использует это как семя.
Итак, если размер небольшого токена в порядке (в случае Random), вы можете продолжать использовать свой код без каких-либо изменений, так как вы используете SecureRandom для генерации семени. Но если вы хотите, чтобы более крупные токены (которые не могут быть подвергнуты brute force attacks), переходите к SecureRandom -
В случае случайных просто 2^48 требуются попытки, с сегодняшним передовым процессором можно разбить его в практическом времени. Но для securerandom 2^128 потребуются попытки, которым потребуются годы и годы, чтобы сломаться даже с современными передовыми машинами.

Подробнее см. .
ИЗМЕНИТЬ
После прочтения ссылок, предоставленных @emboss, ясно, что семя, каким бы случайным оно ни было, не следует использовать с java.util.Random. Очень просто рассчитать семена, наблюдая за выходом.

Перейти для SecureRandom. Используйте Native PRNG (как указано в ссылке выше), потому что для каждого вызова nextBytes() он принимает случайные значения из файла /dev/random. Таким образом, злоумышленник, наблюдающий за выходом, не сможет ничего понять, если он не контролирует содержимое файла /dev/random (что очень маловероятно)
Алгоритм sha1 prng вычисляет семя только один раз, и если ваша виртуальная машина работает в течение нескольких месяцев с использованием одного и того же семени, она может быть взломана злоумышленником, который пассивно наблюдает за выходом. ПРИМЕЧАНИЕ. Если вы вызываете nextBytes() быстрее, чем ваш os способен записывать случайные байты (энтропия) в /dev/random, вы можете столкнуться с проблемами при использовании NATIVE PRNG. В этом случае используйте экземпляр SHA1 PRNG SecureRandom и каждые несколько минут (или некоторый интервал), запустите этот экземпляр со значением от nextBytes() экземпляра NATIVE PRNG SecureRandom. Выполнение этих двух параллелей гарантирует, что вы регулярно посеяны с истинными случайными значениями, а также не исчерпываете энтропию, полученную операционной системой.

Ответ 3

Если вы дважды запустите java.util.Random.nextLong() с тем же самым семенем, он произведет тот же номер. По соображениям безопасности вы хотите придерживаться java.security.SecureRandom, потому что это намного менее предсказуемо.

2 класса похожи, я думаю, вам просто нужно изменить Random на SecureRandom с помощью инструмента рефакторинга, и большая часть вашего существующего кода будет работать.

Ответ 4

Если изменение существующего кода является доступной задачей, я предлагаю вам использовать класс SecureRandom, как предлагается в Javadoc.

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

  • Другие реализации VM делают то же самое.
  • Внедрение класса Random в будущих версиях JDK по-прежнему использует класс SecureRandom

Итак, это лучший выбор, чтобы следовать рекомендациям по документации и перейти непосредственно к SecureRandom.

Ответ 5

Текущая эталонная реализация java.util.Random.nextLong() делает два вызова метода next(int), который непосредственно предоставляет 32 бит текущего семпла:

protected int next(int bits) {
    long nextseed;
    // calculate next seed: ...
    // and store it in the private "seed" field.
    return (int)(nextseed >>> (48 - bits));
}

public long nextLong() {
    // it okay that the bottom word remains signed.
    return ((long)(next(32)) << 32) + next(32);
}

Верхний 32-разрядный результат nextLong() - это бит семени в то время. Поскольку ширина семени составляет 48 бит (говорит javadoc), достаточно * перебрать оставшиеся 16 бит (всего лишь 65.536 попыток) для определения семени, которое произвело второй 32-разрядный бит.

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

Используя вывод nextLong() напрямую, отчасти секрет PNG до такой степени, что весь секрет может быть вычислен с очень небольшим эффектом. Dangerous!

* Требуется некоторое усилие, если второй 32 бит отрицательный, но можно найти это.

Ответ 6

Семя не имеет смысла. Хороший случайный генератор отличается в выбранном примитиве. Каждый случайный генератор начинается с числа и выполняет итерацию через "кольцо". Это означает, что вы приходите от одного номера к другому со старым внутренним значением. Но через некоторое время вы снова начнете снова и начнете все заново. Таким образом, вы запускаете циклы. (возвращаемое значение от случайного генератора не является внутренним значением)

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

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

Другими словами: вы должны заменить все.

Ответ 7

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

Вы никогда не задумывались, как генерируется OTP (одноразовый пароль)? Для генерации OTP мы также используем класс Random и SecureRandom. Теперь, чтобы сделать ваш OTP сильным, SecureRandom лучше, потому что потребовалось 2 ^ 128 попыток, чтобы взломать OTP, что практически невозможно на нынешней машине, но если используется Случайный класс, тогда ваш OTP может быть взломан кем-то, кто может повредить ваши данные, потому что он потребовал просто 2 ^ 48 попробуй, взломать.