Perbedaan antara java.util.Random dan java.security.SecureRandom


202

Tim saya mendapatkan beberapa kode sisi server (di Jawa) yang menghasilkan token acak dan saya memiliki pertanyaan mengenai hal yang sama -

Tujuan dari token-token ini cukup sensitif - digunakan untuk id sesi, tautan setel ulang kata sandi dll. Jadi mereka memang perlu secara kriptografi acak untuk menghindari seseorang menebaknya atau memaksa mereka dengan layak. Tokennya adalah "panjang" sehingga panjangnya 64 bit.

Kode saat ini menggunakan java.util.Randomkelas untuk menghasilkan token ini. The dokumentasi untuk java.util.Randomdengan jelas menyatakan berikut ini:

Contoh java.util.Random tidak aman secara kriptografis. Sebaiknya gunakan SecureRandom untuk mendapatkan generator nomor pseudo-acak yang aman secara kriptografis untuk digunakan oleh aplikasi yang sensitif terhadap keamanan.

Namun, cara kode saat ini menggunakan java.util.Randomini - Ini instantiates java.security.SecureRandomkelas dan kemudian menggunakan SecureRandom.nextLong()metode untuk mendapatkan seed yang digunakan untuk instantiating java.util.Randomkelas. Kemudian ia menggunakan java.util.Random.nextLong()metode untuk menghasilkan token.

Jadi pertanyaan saya sekarang - Apakah masih tidak aman mengingat bahwa java.util.Randomsedang diunggulkan menggunakan java.security.SecureRandom? Apakah saya perlu memodifikasi kode sehingga digunakan java.security.SecureRandomsecara eksklusif untuk menghasilkan token?

Saat ini seed code Randomsekali di startup


14
Setelah diunggulkan, output dari java.util.Random adalah urutan angka deterministik. Anda mungkin tidak menginginkannya.
Peter Štibraný

1
Apakah kode di-seed Randomsekali pada saat startup, atau apakah seed yang baru untuk setiap token? Mudah-mudahan, ini adalah pertanyaan bodoh, tetapi saya pikir saya akan memeriksanya.
Tom Anderson

8
Random hanya memiliki status internal 48-bit dan akan mengulang setelah 2 ^ 48 panggilan ke nextLong () yang berarti tidak akan menghasilkan semua nilai longatau kemungkinan double.
Peter Lawrey

3
Ada masalah parah lainnya. 64bits berarti 1,84 * 10 ^ 19 kemungkinan kombinasi yang terlalu sedikit untuk menahan serangan canggih. Ada mesin di luar sana yang memecahkan kode DES 56 bit (faktor 256 lebih sedikit) dengan 90 * 10 ^ 9 kunci per detik dalam 60 jam. Gunakan 128 bit atau dua long!
Thorsten S.

Jawaban:


232

Implementasi standar Oracle JDK 7 menggunakan apa yang disebut Linear Congruential Generator untuk menghasilkan nilai acak java.util.Random.

Diambil dari java.util.Randomkode sumber (JDK 7u2), dari komentar pada metode protected int next(int bits), yang merupakan yang menghasilkan nilai acak:

Ini adalah generator nomor pseudorandom kongruensial linier, seperti yang didefinisikan oleh DH Lehmer dan dijelaskan oleh Donald E. Knuth dalam The Art of Computer Programming, Volume 3: Algoritma Seminumerik , bagian 3.2.1.

Prediktabilitas Generator Kongruensi Linier

Hugo Krawczyk menulis makalah yang cukup bagus tentang bagaimana LCG ini dapat diprediksi ("Cara memprediksi generator kongruensi"). Jika Anda beruntung dan tertarik, Anda mungkin masih menemukan versi gratis dan dapat diunduh di web. Dan ada banyak lagi penelitian yang dengan jelas menunjukkan bahwa Anda tidak boleh menggunakan LCG untuk tujuan keamanan-kritis. Ini juga berarti bahwa nomor acak Anda dapat diprediksi sekarang, sesuatu yang tidak Anda inginkan untuk ID sesi dan sejenisnya.

Cara memecahkan Linear Congruential Generator

Asumsi bahwa penyerang harus menunggu LCG untuk mengulang setelah siklus penuh salah. Bahkan dengan siklus yang optimal (modulus m dalam relasinya yang berulang) sangat mudah untuk memprediksi nilai masa depan dalam waktu yang jauh lebih sedikit daripada siklus penuh. Bagaimanapun, itu hanya sekelompok persamaan modular yang perlu dipecahkan, yang menjadi mudah segera setelah Anda mengamati nilai output yang cukup dari LCG.

Keamanan tidak membaik dengan benih "lebih baik". Tidak masalah jika Anda menyemai dengan nilai acak yang dihasilkan oleh SecureRandomatau bahkan menghasilkan nilai dengan menggulirkan dadu beberapa kali.

Seorang penyerang hanya akan menghitung seed dari nilai output yang diamati. Ini memakan waktu kurang dari 2 ^ 48 secara signifikan dalam kasus java.util.Random. Orang-orang yang tidak percaya dapat mencoba eksperimen ini , di mana ditunjukkan bahwa Anda dapat memprediksi Randomkeluaran di masa depan dengan hanya mengamati dua (!) Nilai keluaran dalam waktu kira-kira 2 ^ 16. Bahkan tidak perlu sedetik pun pada komputer modern untuk memprediksi output angka acak Anda saat ini.

Kesimpulan

Ganti kode Anda saat ini. Gunakan SecureRandomsecara eksklusif. Maka setidaknya Anda akan memiliki sedikit jaminan bahwa hasilnya akan sulit diprediksi. Jika Anda menginginkan properti PRNG yang aman secara kriptografis (dalam kasus Anda, itulah yang Anda inginkan), maka Anda SecureRandomhanya perlu menggunakannya. Menjadi pandai mengubah cara seharusnya digunakan akan hampir selalu menghasilkan sesuatu yang kurang aman ...


4
Sangat membantu, mungkin Anda juga bisa menjelaskan cara kerja SecureRandom (seperti halnya Anda menjelaskan cara kerja Random).
gresdiplitude

4
Itu mengalahkan tujuan secureRandom
Azulflame

Saya tahu, mempelajari pelajaran itu dengan cara yang sulit. Tapi sumber kode sulit dan sulit ditemukan bekerja dengan baik. Notch dapat mempelajari sesuatu tentang hal itu (dia menyandikan kata sandi penggunanya dalam file .lastlogin, yang disandikan dengan enkripsi dasar menggunakan "kata sandi file" sebagai kuncinya)
Azulflame

1
Pertanyaan sebenarnya di sini: jika java dapat menghasilkan prng yang lebih aman dengan API serupa, mengapa mereka tidak mengganti yang rusak?
Joel Coehoorn

11
@ JoelCoehoorn Bukan itu Randomrusak - itu hanya harus digunakan dalam skenario yang berbeda. Tentu saja, Anda selalu dapat menggunakan SecureRandom. Namun secara umum, SecureRandomterasa lebih lambat daripada murni Random. Dan ada beberapa kasus di mana Anda hanya tertarik pada properti statistik yang bagus dan kinerja yang sangat baik, tetapi Anda tidak terlalu peduli dengan keamanan: simulasi Monte-Carlo adalah contoh yang baik. Saya membuat komentar tentang itu dalam jawaban yang sama , mungkin Anda akan merasa berguna.
emboss

72

Acak hanya memiliki 48 bit sedangkan SecureRandom dapat memiliki hingga 128 bit. Jadi peluang pengulangan dalam securerandom sangat kecil.

Acak menggunakan system clocksebagai benih / atau untuk menghasilkan benih. Jadi mereka dapat direproduksi dengan mudah jika penyerang tahu waktu di mana benih itu dihasilkan. Tapi SecureRandom mengambil Random Datadari Anda os(mereka dapat interval antara penekanan tombol dll - sebagian besar mengumpulkan data ini menyimpannya dalam file - /dev/random and /dev/urandom in case of linux/solaris) dan menggunakannya sebagai seed.
Jadi jika ukuran token kecil baik-baik saja (dalam kasus Acak), Anda dapat terus menggunakan kode Anda tanpa perubahan, karena Anda menggunakan SecureRandom untuk menghasilkan seed. Tetapi jika Anda ingin token yang lebih besar (yang tidak dapat dikenakan brute force attacks) lanjutkan dengan SecureRandom -
Dalam kasus 2^48upaya acak saja diperlukan, dengan cpu canggih saat ini dimungkinkan untuk memecahkannya dalam waktu praktis. Tetapi untuk 2^128upaya securerandom akan diperlukan, yang akan membutuhkan waktu bertahun-tahun untuk mencapai titik impas dengan mesin canggih saat ini.

Lihat tautan ini untuk lebih jelasnya.
EDIT
Setelah membaca tautan yang disediakan oleh @emboss, jelas bahwa seed, betapapun acaknya, tidak boleh digunakan dengan java.util.Random. Sangat mudah untuk menghitung benih dengan mengamati hasilnya.

Go for SecureRandom - Gunakan PRNG Asli (seperti yang diberikan dalam tautan di atas) karena mengambil nilai acak dari /dev/randomfile untuk setiap panggilan kenextBytes(). Dengan cara ini seorang penyerang mengamati output tidak akan dapat membuat apa-apa kecuali ia mengendalikan isi dari /dev/randomberkas (yang sangat tidak mungkin)
The sha1 PRNG algoritma menghitung benih hanya sekali dan jika VM Anda berjalan selama berbulan-bulan menggunakan yang sama benih, itu mungkin retak oleh penyerang yang secara pasif mengamati output.

CATATAN - Jika Anda memanggil yang nextBytes()lebih cepat daripada os Anda dapat menulis byte acak (entropi) ke dalam /dev/random, Anda mungkin mendapat masalah saat menggunakan NATIVE PRNG . Dalam hal itu gunakan instance SHA1 PRNG dari SecureRandom dan setiap beberapa menit (atau interval tertentu), seed instance ini dengan nilai darinextBytes()dari instance PRNG NATIVE dari SecureRandom. Menjalankan keduanya secara paralel akan memastikan bahwa Anda melakukan seeding secara teratur dengan nilai acak yang benar, sementara juga tidak melelahkan entropi yang diperoleh oleh Sistem Operasi.


Diperlukan jauh lebih sedikit dari 2 ^ 48 untuk memprediksi Random, OP seharusnya tidak menggunakan Randomsama sekali.
emboss

@emboss: Saya berbicara tentang bruteforce.
Ashwin

1
Berhati-hatilah dengan Linux: ia dapat mencapai kelelahan entropi (lebih banyak di VM daripada dengan perangkat keras)! Lihatlah /proc/sys/kernel/random/entropy_availdan periksa dengan beberapa kesedihan utas bahwa tidak ada menunggu terlalu lama saat membaca terus/dev/random
Yves Martin

2
Perhatikan bahwa Oracle JRE (setidaknya 1,7) bekerja dengan / dev / urandom secara default dan bukan / dev / random sehingga akhiran jawaban Anda tidak lagi benar. untuk memverifikasi periksa $ JAVA_HOME / lib / security / java.security untuk properti securerandom.source
Boaz

1
File java.security kami memiliki securerandom.source = file: / dev / urandom alih-alih file: /// dev / urandom (dua garis miring setelah titik dua untuk protokol file, lalu satu lagi garis miring untuk root dari sistem file), menyebabkannya mundur ke / dev / random, yang menyebabkan masalah dengan kelelahan pool entropi. Tidak dapat mengeditnya, jadi harus mengatur properti sistem java.security.egd ke yang benar saat startup aplikasi.
maxpolk

11

Jika Anda menjalankan dua kali java.util.Random.nextLong()dengan seed yang sama, itu akan menghasilkan angka yang sama. Untuk alasan keamanan Anda ingin tetap menggunakannya java.security.SecureRandomkarena jauh lebih mudah diprediksi.

2 Kelas serupa, saya pikir Anda hanya perlu perubahan Randomuntuk SecureRandomdengan alat refactoring dan sebagian besar kode yang ada akan bekerja.


11
Jika Anda mengambil dua instance dari PRNG dan menaburnya dengan nilai yang sama Anda selalu mendapatkan nomor acak yang sama, bahkan menggunakan SecureRandom tidak mengubah itu. Semua PRNG bersifat deterministik dan karenanya dapat diprediksi jika Anda mengetahui benihnya.
Robert

1
Ada implementasi SecureRandom yang berbeda, ada yang PRNG, ada juga yang tidak. Di sisi lain, java.util.Random selalu PRNG (sebagaimana didefinisikan dalam Javadoc-nya).
Peter Štibraný

3

Jika mengubah kode Anda yang ada adalah tugas yang terjangkau, saya sarankan Anda menggunakan kelas SecureRandom seperti yang disarankan di Javadoc.

Bahkan jika Anda menemukan implementasi kelas acak menggunakan kelas SecureRandom secara internal. Anda tidak harus menerima begitu saja bahwa:

  1. Implementasi VM lain melakukan hal yang sama.
  2. Implementasi kelas acak di versi masa depan JDK masih menggunakan kelas SecureRandom

Jadi itu adalah pilihan yang lebih baik untuk mengikuti saran dokumentasi dan langsung menggunakan SecureRandom.


Saya tidak percaya pertanyaan awal menyatakan bahwa java.util.Random implementasi digunakan secara SecureRandominternal, katanya kode mereka gunakan SecureRandomuntuk seed Random. Tetap saja, saya setuju dengan kedua jawaban sejauh ini; lebih baik digunakan SecureRandomuntuk menghindari solusi deterministik eksplisit.
Palpatim

2

Implementasi referensi saat ini java.util.Random.nextLong()membuat dua panggilan ke metode next(int)yang secara langsung memperlihatkan 32 bit dari seed saat ini:

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 bit atas dari hasil nextLong() adalah bit benih pada saat itu. Karena lebar benih adalah 48 bit (kata javadoc), cukup * untuk beralih di atas 16 bit yang tersisa (itu hanya 65,536 mencoba) untuk menentukan benih yang menghasilkan 32 bit kedua.

Setelah benih diketahui, semua token berikut dapat dengan mudah dihitung.

Menggunakan output nextLong()secara langsung, sebagian dari rahasia PNG hingga tingkat bahwa seluruh rahasia dapat dihitung dengan sedikit usaha. Berbahaya!

* Ada beberapa upaya yang diperlukan jika 32 bit kedua negatif, tetapi orang dapat menemukannya.


Benar. Lihat cara memecahkan java.util.random dengan cepat di jazzy.id.au/default/2010/09/20/… !
mana

2

Benih tidak ada artinya. Generator acak yang baik berbeda dengan primenumber yang dipilih. Setiap generator acak dimulai dari angka dan beralih melalui 'cincin'. Yang berarti, Anda berasal dari satu nomor ke yang berikutnya, dengan nilai internal yang lama. Tetapi setelah beberapa saat Anda mencapai awal lagi dan memulai dari awal lagi. Jadi, Anda menjalankan siklus. (nilai pengembalian dari generator acak bukan nilai internal)

Jika Anda menggunakan bilangan prima untuk membuat cincin, semua angka dalam cincin itu dipilih, sebelum Anda menyelesaikan siklus penuh melalui semua angka yang mungkin. Jika Anda mengambil nomor non utama, tidak semua nomor dipilih dan Anda mendapatkan siklus yang lebih pendek.

Angka prima yang lebih tinggi berarti, siklus yang lebih lama, sebelum Anda kembali ke elemen pertama lagi. Jadi, generator acak aman hanya memiliki siklus yang lebih lama, sebelum mencapai awal lagi, itu sebabnya lebih aman. Anda tidak dapat memprediksi pembuatan angka semudah dengan siklus yang lebih pendek.

Dengan kata lain: Anda harus mengganti semua.


0

Saya akan mencoba menggunakan kata-kata yang sangat mendasar sehingga Anda dapat dengan mudah memahami perbedaan antara Random dan secureRandom dan pentingnya SecureRandom Class.

Pernah bertanya-tanya bagaimana OTP (kata sandi satu kali) dihasilkan? Untuk menghasilkan OTP, kami juga menggunakan kelas Random dan SecureRandom. Sekarang untuk membuat OTP Anda kuat, SecureRandom lebih baik karena butuh 2 ^ 128 mencoba, untuk memecahkan OTP yang hampir tidak mungkin oleh mesin sekarang tetapi jika menggunakan Kelas Acak maka OTP Anda dapat dipecahkan oleh seseorang yang dapat membahayakan data Anda karena butuh hanya 2 ^ 48 mencoba, untuk memecahkan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.