243.583.606.221.817.150.598.111.409x lebih banyak entropi
Saya akan merekomendasikan menggunakan crypto.randomBytes . Tidak sha1
, tapi untuk tujuan id, ini lebih cepat, dan sama "acaknya".
var id = crypto.randomBytes(20).toString('hex');
String yang dihasilkan akan dua kali lebih panjang dari byte acak yang Anda hasilkan; setiap byte yang dikodekan menjadi hex adalah 2 karakter. 20 byte akan menjadi 40 karakter hex.
Menggunakan 20 byte, kami memiliki 256^20
atau 1.461.501.637.330.902.918.203.684.832.716.283.019.655.932.542.976 nilai keluaran unik. Ini identik dengan kemungkinan output SHA1 160-bit (20-byte).
Mengetahui hal ini, tidak terlalu berarti bagi kami untuk shasum
byte acak kami. Ini seperti melempar dadu dua kali tetapi hanya menerima lemparan kedua; Apa pun yang terjadi, Anda memiliki 6 kemungkinan hasil setiap lemparan, jadi lemparan pertama sudah cukup.
Mengapa ini lebih baik?
Untuk memahami mengapa ini lebih baik, pertama-tama kita harus memahami cara kerja fungsi hashing. Fungsi hashing (termasuk SHA1) akan selalu menghasilkan keluaran yang sama jika masukan yang sama diberikan.
Katakanlah kita ingin menghasilkan ID tetapi input acak kita dihasilkan oleh lemparan koin. Kami memiliki "heads"
atau"tails"
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
% echo -n "tails" | shasum
71ac9eed6a76a285ae035fe84a251d56ae9485a4 -
Jika "heads"
muncul lagi, output SHA1 akan sama seperti saat pertama kali
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
Ok, jadi lemparan koin bukanlah generator ID acak yang bagus karena kita hanya memiliki 2 kemungkinan keluaran.
Jika kami menggunakan cetakan 6-sisi standar, kami memiliki 6 kemungkinan input. Tebak berapa banyak kemungkinan keluaran SHA1? 6!
input => (sha1) => output
1 => 356a192b7913b04c54574d18c28d46e6395428ab
2 => da4b9237bacccdf19c0760cab7aec4a8359010b0
3 => 77de68daecd823babbb58edb1c8e14d7106e83bb
4 => 1b6453892473a467d07372d45eb05abc2031647a
5 => ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4
6 => c1dfd96eea8cc2b62785275bca38ac261256e278
Sangat mudah untuk menipu diri sendiri dengan berpikir hanya karena output dari fungsi kita terlihat sangat acak, bahwa itu adalah sangat acak.
Kami berdua setuju bahwa lemparan koin atau dadu 6 sisi akan menjadi generator id acak yang buruk, karena kemungkinan hasil SHA1 kami (nilai yang kami gunakan untuk ID) sangat sedikit. Tetapi bagaimana jika kita menggunakan sesuatu yang memiliki keluaran lebih banyak? Seperti stempel waktu dengan milidetik? Atau JavaScript Math.random
? Atau bahkan kombinasi keduanya ?!
Mari kita hitung berapa banyak id unik yang akan kita dapatkan ...
Keunikan stempel waktu dengan milidetik
Saat menggunakan (new Date()).valueOf().toString()
, Anda mendapatkan nomor 13 karakter (misalnya, 1375369309741
). Namun, karena ini adalah nomor yang diperbarui secara berurutan (sekali per milidetik), hasilnya hampir selalu sama. Mari lihat
for (var i=0; i<10; i++) {
console.log((new Date()).valueOf().toString());
}
console.log("OMG so not random");
Agar adil, untuk tujuan perbandingan, dalam satu menit (waktu pelaksanaan operasi yang murah hati), Anda akan memiliki 60*1000
atau 60000
unik.
Keunikan Math.random
Sekarang, saat menggunakan Math.random
, karena cara JavaScript mewakili bilangan titik mengambang 64-bit, Anda akan mendapatkan nomor dengan panjang antara 13 dan 24 karakter. Hasil yang lebih panjang berarti lebih banyak digit yang berarti lebih banyak entropi. Pertama, kita perlu mencari tahu panjang mana yang paling memungkinkan.
Skrip di bawah ini akan menentukan panjang mana yang paling memungkinkan. Kami melakukan ini dengan menghasilkan 1 juta nomor acak dan menambah penghitung berdasarkan .length
jumlah masing-masing nomor.
var counts = [], rand, len;
for (var i=0; i<1000000; i++) {
rand = Math.random();
len = String(rand).length;
if (counts[len] === undefined) counts[len] = 0;
counts[len] += 1;
}
var freq = counts.map(function(n) { return n/1000000 *100 });
Dengan membagi setiap penghitung dengan 1 juta, kita mendapatkan probabilitas panjang angka yang dikembalikan Math.random
.
len frequency(%)
------------------
13 0.0004
14 0.0066
15 0.0654
16 0.6768
17 6.6703
18 61.133 <- highest probability
19 28.089 <- second highest probability
20 3.0287
21 0.2989
22 0.0262
23 0.0040
24 0.0004
Jadi, meskipun itu tidak sepenuhnya benar, mari bermurah hati dan katakan Anda mendapatkan keluaran acak sepanjang 19 karakter; 0.1234567890123456789
. Karakter pertama akan selalu 0
dan .
, jadi kami hanya mendapatkan 17 karakter acak. Ini menyisakan 10^17
+1
(untuk kemungkinan 0
; lihat catatan di bawah) atau 100.000.000.000.000.001 unik.
Jadi, berapa banyak masukan acak yang dapat kita hasilkan?
Oke, kami menghitung jumlah hasil untuk stempel waktu milidetik dan Math.random
100,000,000,000,000,001 (Math.random)
* 60,000 (timestamp)
-----------------------------
6,000,000,000,000,000,060,000
Itu adalah satu mati 6.000.000.000.000.000.000.060.000 sisi. Atau, untuk membuat angka ini lebih mudah dicerna, ini kira - kira sama dengan angka
input outputs
------------------------------------------------------------------------------
( 1×) 6,000,000,000,000,000,060,000-sided die 6,000,000,000,000,000,060,000
(28×) 6-sided die 6,140,942,214,464,815,497,21
(72×) 2-sided coins 4,722,366,482,869,645,213,696
Kedengarannya cukup bagus, bukan? Nah, mari kita cari tahu ...
SHA1 menghasilkan nilai 20-byte, dengan kemungkinan hasil 256 ^ 20. Jadi kami benar-benar tidak menggunakan SHA1 secara maksimal. Berapa banyak yang kita gunakan?
node> 6000000000000000060000 / Math.pow(256,20) * 100
Stempel waktu milidetik dan Math.random hanya menggunakan 4,11e-27 persen dari potensi 160-bit SHA1!
generator sha1 potential used
-----------------------------------------------------------------------------
crypto.randomBytes(20) 100%
Date() + Math.random() 0.00000000000000000000000000411%
6-sided die 0.000000000000000000000000000000000000000000000411%
A coin 0.000000000000000000000000000000000000000000000137%
Kucing suci, bung! Lihat semua angka nol itu. Jadi seberapa jauh lebih baik itu crypto.randomBytes(20)
? 243.583.606.221.817.150.598.111.409 kali lebih baik.
Catatan tentang +1
dan frekuensi nol
Jika Anda bertanya-tanya tentang +1
, itu mungkin untuk Math.random
mengembalikan 0
yang berarti ada 1 lagi kemungkinan hasil unik yang harus kami perhitungkan.
Berdasarkan pembahasan yang terjadi di bawah ini, saya penasaran dengan frekuensi yang 0
akan muncul. Ini sedikit script random_zero.js
,, saya buat untuk mendapatkan beberapa data
#!/usr/bin/env node
var count = 0;
while (Math.random() !== 0) count++;
console.log(count);
Kemudian, saya menjalankannya dalam 4 utas (saya memiliki prosesor 4-inti), menambahkan output ke file
$ yes | xargs -n 1 -P 4 node random_zero.js >> zeroes.txt
Jadi ternyata a 0
tidak sulit didapat. Setelah 100 nilai dicatat, rata-rata adalah
1 dari 3.164.854.823 randoms adalah 0
Keren! Diperlukan lebih banyak penelitian untuk mengetahui apakah angka itu setara dengan distribusi seragam Math.random
implementasi v8