Разница между Java.утиль.Random и java.безопасность.SecureRandom отобрать

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

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

код в настоящее время используется java.util.Random класс для генерации маркеров. Документация ([http://docs.oracle.com/javase/7/docs/api/java/util/Random.html][1]) для java.util.Random четко указано следующее:

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

однако, как код в настоящее время использует java.util.Random Это - он создает the java.security.SecureRandom класс, а затем использует SecureRandom.nextLong() метод для получения семени, которое используется для создания экземпляра java.util.Randomкласса. Тогда он использует java.util.Random.nextLong() метод для генерации токена.

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

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

7 ответов


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

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

это линейный конгруэнтный генератор псевдослучайных чисел, как определяется D. H. Lehmer и описывается Дональдом Э. кнутом в искусство компьютера Программирование, объем 3: Получисловые Алгоритмы, раздел 3.2.1.

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

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

как сломать линейный конгруэнтный генератор

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

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

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

вывод

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


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

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

См. этой ссылке для более подробной информации.
редактировать
После прочтения ссылок, предоставленных @emboss, ясно, что семя, каким бы случайным оно ни было, не следует использовать с Java.утиль.Случайность. Очень легко высчитать семя путем наблюдать выходом.

перейти на SecureRandom - используйте родной PRNG (как указано в ссылка выше), потому что она принимает случайные значения из /dev/random файл для каждого вызова nextBytes(). Таким образом, злоумышленник, наблюдающий за выходом, не сможет ничего разобрать, если он не контролирует содержимое /dev/random файл (что очень маловероятно)
The SHA1 для ГПСЧ алгоритм вычисляет семя только один раз, и если ваша виртуальная машина работает в течение нескольких месяцев, используя одно и то же семя, оно может быть взломано злоумышленником, который пассивно наблюдает за выход.

Примечание - если вы вызываете nextBytes() быстрее, чем ваша ОС способна записывать случайные байты (энтропия) в /dev/random, вы можете попасть в беду при использовании РОДНОЙ PRNG. В этом случае используйте экземпляр SHA1 PRNG SecureRandom и каждые несколько минут (или некоторый интервал), засевайте этот экземпляр значением из nextBytes() собственного экземпляра PRNG SecureRandom. Выполнение этих двух параллельно гарантирует, что вы регулярно заполняете true случайные величины, при этом также не исчерпывающие энтропию, полученную операционной системой.


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

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


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

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

  1. другие реализации VM делают то же самое.
  2. реализация случайного класса в будущих версиях JDK по-прежнему использует класс SecureRandom

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


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

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

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

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


текущая справочная реализация 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's okay that the bottom word remains signed.
    return ((long)(next(32)) << 32) + next(32);
}

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

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

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

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


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

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