Різниця між java.util.Random і java.security.SecureRandom


202

Моїй команді передали код із боку сервера (на 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один раз при запуску


14
Після засіву, вихід з java.util.Random є детермінованою послідовністю чисел. Можливо, ви цього не хочете.
Петро Штібрані

1
Чи код засіває Randomодин раз під час запуску, або ж він створює новий на кожен маркер? Сподіваюся, це дурне питання, але я думав, що перевірю.
Том Андерсон

8
Random має лише 48-бітний внутрішній стан і повториться після 2 ^ 48 викликів до nextLong (), що означає, що він не видасть усіх можливих longабо doubleзначень.
Пітер Лоурі

3
Є ще одна серйозна проблема. 64 біт означає 1,84 * 10 ^ 19 можливих комбінацій, що замало, щоб протистояти складній атаці. Є машини, які тріснули 56-бітний код DES (коефіцієнт 256 менше) з 90 * 10 ^ 9 клавішами в секунду за 60 годин. Використовуйте 128 біт або два довгих!
Торстен С.

Відповіді:


232

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

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

Це генератор лінійних конгрурентних псевдовипадкових чисел, визначений DH Лемер та описаний Дональдом Е. Кнутом у "Мистецтві комп'ютерного програмування", Том 3: Напівлінійні алгоритми , розділ 3.2.1.

Передбачуваність лінійних конгрурентних генераторів

Уго Кравчик написав досить непоганий документ про те, як можна прогнозувати ці ЖКГ ("Як прогнозувати конгруентні генератори"). Якщо вам пощастило та зацікавлено, ви все ще можете знайти в Інтернеті безкоштовну завантажувану його версію. І ще багато досліджень, які наочно показують, що ніколи не слід використовувати ЖКГ у критичних для безпеки цілях. Це також означає, що ваші випадкові номери зараз передбачувані, що вам не потрібно для ідентифікаторів сеансу тощо.

Як зламати лінійний конгрурентний генератор

Припущення про те, що зловмиснику доведеться чекати, коли LCG повториться після повного циклу, є помилковим. Навіть при оптимальному циклі (модуль m у відношенні рецидиву) дуже легко передбачити майбутні значення за набагато менший час, ніж повний цикл. Зрештою, це лише купа модульних рівнянь, які потрібно вирішити, що стає простим, як тільки ви помітили достатню вихідну величину LCG.

Захист не покращується за допомогою «кращого» насіння. Це просто не має значення, якщо ви сіяєте з випадковим значенням, що генерується SecureRandomабо навіть виробляє цінність, прокочуючи штамб кілька разів.

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

Висновок

Замініть поточний код. Використовуйте SecureRandomвиключно. Тоді принаймні у вас буде невелика гарантія, що результат буде важко передбачити. Якщо ви хочете, щоб властивості криптографічно захищеного PRNG (у вашому випадку це саме те, що вам потрібно), тоді вам доведеться йти SecureRandomлише з цим. Розумний у зміні способу його використання майже завжди призведе до чогось менш безпечного ...


4
Дуже корисно, ви можете також пояснити, як працює SecureRandom (так само, як ви пояснюєте, як працює Random) ..
gresdiplitude

4
Це перемагає мету secureRandom
Azulflame

Я знаю, засвоїв це заняття важким шляхом. Але міцний цифер і важко знайти джерело працює добре. Виїмка може дізнатися що - то про те , що (він кодує пароль свого користувача в файлі .lastlogin, закодований з використанням базового шифрування «PasswordFile» в якості ключа)
Azulflame

1
Справжнє питання тут: якщо java може створити більш безпечний prng з подібним API, чому вони просто не замінили зламану?
Джоел Куехорн

11
@JoelCoehoorn Це не те, що Randomпорушено - його слід просто використовувати в різних сценаріях. Звичайно, ви завжди можете використовувати SecureRandom. Але загалом SecureRandomпомітно повільніше, ніж чисте Random. І бувають випадки, коли вас цікавлять лише хороші статистичні властивості та відмінна продуктивність, але про безпеку вас не цікавить: імітація Монте-Карло - хороший приклад. Я коментував це у подібній відповіді , можливо, вам це стане в нагоді.
тиснення

72

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

Випадковий використовує 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спроб, з сучасними вдосконаленими процесорами можна розбити це в практичний час. Але для безпечних 2^128спроб знадобляться спроби, на які знадобляться роки і роки, щоб навіть з сучасними сучасними машинами перерватися.

Дивіться це посилання для отримання більш детальної інформації.
EDIT
Після прочитання посилань, наданих @emboss, зрозуміло, що насіння, яким би випадковим воно не було, не повинно використовуватися з java.util.Random. Розрахувати насіння дуже просто, спостерігаючи за виходом.

Перейдіть на SecureRandom - використовуйте Native PRNG (як зазначено у посиланні вище), оскільки /dev/randomдля кожного виклику вони беруть випадкові значення з файлуnextBytes(). Таким чином, зловмисник, що спостерігає за результатами, не зможе зробити нічого, якщо він не контролює вміст /dev/randomфайлу (що дуже малоймовірно)
Алгоритм sha1 prng обчислює насіння лише один раз, і якщо ваш VM працює місяцями, використовуючи той самий Насіння, це може зламатися зловмисником, який пасивно спостерігає за результатами.

ПРИМІТКА - Якщо ви телефонуєте зіткнутися з NATIVE PRNG . У такому випадку використовуйте примірник SHA1 PRNG SecureRandom і кожні кілька хвилин (або деякий інтервал) закладайте цей екземпляр зі значенням відnextBytes()швидше, ніж ваш оператор може вписати випадкові байти (ентропію) у /dev/random, ви можете проблемою під час використання nextBytes()екземпляру NATIVE PRNG SecureRandom. Виконання цих двох паралельно забезпечить регулярне висівання справжніх випадкових значень, а також не вичерпає ентропію, отриману Операційною системою.


Для прогнозування a потрібно набагато менше 2 ^ 48 Random, ОП взагалі не слід використовувати Random.
тиснення

@emboss: Я говорю про грубу силу.
Ешвін

1
Будьте обережні з Linux: він може досягти ентропійного виснаження (більше в VM, ніж з апаратним забезпеченням)! Подивіться /proc/sys/kernel/random/entropy_availта переконайтесь, що демпфери ниток не надто довго чекають, читаючи далі/dev/random
Ів Мартін,

2
Зауважте, що Oracle JRE (принаймні 1,7) працює з / dev / urandom за замовчуванням, а не / dev / random, тому суфікс вашої відповіді більше не правильний. щоб перевірити перевірку $ JAVA_HOME / lib / security / java.security для securerandom.source властивості
Boaz

1
У нашому файлі java.security був securerandom.source = файл: / dev / urandom замість файлу: /// dev / urandom (два косі риски після двокрапки для файлового протоколу, потім ще одна коса риса для кореня файлової системи), що призводить до відпадання до / dev / random, що спричинило проблеми з виснаженням пулу ентропії. Не вдалося його відредагувати, тому довелося встановити властивість системи java.security.egd на правильну при запуску програми.
maxpolk

11

Якщо ви два рази запускаєтесь java.util.Random.nextLong()з одним і тим же насінням, воно дасть однакову кількість. З міркувань безпеки ви хочете дотримуватися, java.security.SecureRandomтому що це набагато менш передбачувано.

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


11
Якщо ви візьмете два екземпляри будь-якого PRNG і закладаєте його з однаковим значенням, ви завжди отримуєте однакові випадкові числа, навіть використання SecureRandom цього не змінює. Усі PRNG є детермінованими і тому передбачуваними, якщо ви знаєте насіння.
Роберт

1
Існують різні реалізації SecureRandom, деякі - PRNG, деякі - ні. З іншого боку, java.util.Random - це завжди PRNG (як визначено в його Javadoc).
Петро Штібрані

3

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

Навіть якщо ви виявите, що реалізація класу Random використовує клас SecureRandom внутрішньо. не слід сприймати це як належне, що:

  1. Інші реалізації VM роблять те саме.
  2. Реалізація класу Random у майбутніх версіях JDK досі використовує клас SecureRandom

Тож кращий вибір - дотримуватися пропозицій щодо документації та прямувати із SecureRandom.


Я не вірю, що в первісному запитанні було вказано, що java.util.Randomреалізація використовується SecureRandomвнутрішньо, і він сказав, що їх код використовує SecureRandomдля виведення на екран Random. Все-таки я згоден з обома відповідями поки що; найкраще використовувати, SecureRandomщоб уникнути явно детермінованого рішення.
Палпатім

2

Поточна реалізація посилань 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), для визначення насіння, яке дало другий 32 біт, достатньо * перебирати інші 16 біт (це всього 65.536 спроб).

Як тільки насіння відомо, всі наступні жетони можна легко обчислити.

Використовуючи висновок nextLong()прямо, частково секрету PNG, настільки, що весь секрет можна обчислити з дуже невеликими зусиллями. Небезпечно!

* Потрібно докласти певних зусиль, якщо другий 32 біт негативний, але це можна дізнатися.


Правильно. Дивіться, як швидко зламати java.util.random на jazzy.id.au/default/2010/09/20/… !
ingyhere

2

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

Якщо ви використовуєте просте число для створення кільця, всі номери в цьому кільці вибираються, перш ніж пройти повний цикл через усі можливі числа. Якщо ви приймаєте непрості числа, вибираються не всі числа, і ви отримуєте коротші цикли.

Більш високі прості числа означають, триваліші цикли, перш ніж знову повернутися до першого елемента. Отже, безпечний випадковий генератор просто має більш тривалий цикл, перш ніж знову доходити до початку, ось чому він безпечніший. Ви не можете передбачити генерацію чисел так просто, як за допомогою коротших циклів.

Іншими словами: Ви повинні замінити всіх.


0

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

Ніколи не замислюєтеся, як генерується OTP (одноразовий пароль)? Для створення OTP ми також використовуємо Random та SecureRandom. Тепер, щоб зробити ваш OTP міцним, SecureRandom краще, тому що потрібно 2 ^ 128 спроб, щоб зламати OTP, що майже неможливо в теперішній машині, але якщо він використовується Random Class, то ваш OTP може бути зламаний тим, хто може завдати шкоди вашим даним, тому що це потрібно всього 2 ^ 48 спробуй зламати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.