243,583,606,221,817,150,598,111,409x більше ентропії
Я рекомендую використовувати crypto.randomBytes . Це не так sha1
, але в цілях ідентифікації це швидше і так само «випадково».
var id = crypto.randomBytes(20).toString('hex');
//=> f26d60305dae929ef8640a75e70dd78ab809cfe9
Отриманий рядок буде вдвічі довшим, ніж генеровані випадковими байтами; кожен байт, закодований у шістнадцятку, - це 2 символи. 20 байт становитиме 40 символів шістнадцяткової.
Використовуючи 20 байт, ми маємо 256^20
або 1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976 унікальних вихідних значень. Це ідентично можливим 160-бітовим (20-байтовим) виходу SHA1.
Знаючи це, для нас shasum
випадкові байти насправді не мають сенсу . Це як закатати штамп двічі, але лише прийняти другий рулон; незважаючи ні на що, у вас є 6 можливих результатів для кожного випуску, тому першого виклику достатньо.
Чому це краще?
Щоб зрозуміти, чому це краще, спершу треба зрозуміти, як працюють хеширующие функції. Функції хешування (включаючи SHA1) завжди будуть генерувати один і той же вихід, якщо подається однаковий вхід.
Скажімо, ми хочемо генерувати ідентифікатори, але наш випадковий вхід генерується кидком монети. У нас є "heads"
або"tails"
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
% echo -n "tails" | shasum
71ac9eed6a76a285ae035fe84a251d56ae9485a4 -
Якщо "heads"
з'явиться знову, вихід SHA1 буде таким самим, як і вперше
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
Гаразд, тому кидання монети не є великим генератором випадкових ідентифікаторів, оскільки у нас є лише 2 можливих виходи.
Якщо ми використовуємо стандартну 6-сторонній штамп, у нас є 6 можливих входів. Відгадайте, скільки можливих виходів SHA1? 6!
input => (sha1) => output
1 => 356a192b7913b04c54574d18c28d46e6395428ab
2 => da4b9237bacccdf19c0760cab7aec4a8359010b0
3 => 77de68daecd823babbb58edb1c8e14d7106e83bb
4 => 1b6453892473a467d07372d45eb05abc2031647a
5 => ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4
6 => c1dfd96eea8cc2b62785275bca38ac261256e278
Це легко обманювати себе, думаючи тільки тому , що вихід наших функцій виглядають дуже випадково, що вона є дуже випадковою.
Ми обоє погоджуємось, що кидання монети або 6-сторонній штамб призведе до поганого генератора випадкових ідентифікаторів, оскільки наші можливі результати SHA1 (значення, яке ми використовуємо для ідентифікатора) дуже мало. Але що робити, якщо ми використовуємо щось, що має набагато більше результатів? Як часова мітка з мілісекундами? Або JavaScript Math.random
? Або навіть поєднання цих двох ?!
Давайте обчислимо, скільки унікальних ідентифікаторів ми отримаємо ...
Унікальність часової позначки з мілісекундами
Під час використання (new Date()).valueOf().toString()
ви отримуєте 13-символьний номер (наприклад, 1375369309741
). Однак, оскільки це послідовно оновлене число (один раз на мілісекунду), результати майже завжди однакові. Давайте подивимось
for (var i=0; i<10; i++) {
console.log((new Date()).valueOf().toString());
}
console.log("OMG so not random");
// 1375369431838
// 1375369431839
// 1375369431839
// 1375369431839
// 1375369431839
// 1375369431839
// 1375369431839
// 1375369431839
// 1375369431840
// 1375369431840
// OMG so not random
Якщо бути справедливим, для порівняння, за дану хвилину (щедрий час виконання операції) у вас з'являться 60*1000
або 60000
унікальні речі .
Унікальність Math.random
Тепер, використовуючи Math.random
, через те, як JavaScript представляє 64-бітні числа з плаваючою точкою, ви отримаєте число довжиною в будь-якому місці від 13 до 24 символів. Більш довгий результат означає більше цифр, що означає більше ентропії. Спочатку нам потрібно з’ясувати, яка найбільш вірогідна довжина.
Сценарій нижче визначає, яка довжина найбільш вірогідна. Ми робимо це, генеруючи 1 мільйон випадкових чисел і збільшуючи лічильник на основі .length
кожного числа.
// get distribution
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;
}
// calculate % frequency
var freq = counts.map(function(n) { return n/1000000 *100 });
Розділивши кожен лічильник на 1 мільйон, ми отримуємо ймовірність довжини числа, повернутого з 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
Тож, незважаючи на те, що це не зовсім правда, давайте будемо щедрі та скажемо, що ви отримуєте випадковий вихід з 19 символів; 0.1234567890123456789
. Перші символи завжди будуть, 0
і .
тому насправді ми отримуємо лише 17 випадкових символів. Це залишає нам 10^17
+1
(можливо 0
, див. Примітки нижче) або 100 000 000 000 000 001 унікальних предметів.
Так скільки випадкових входів ми можемо генерувати?
Гаразд, ми обчислили кількість результатів для мітки секунди та Math.random
100,000,000,000,000,001 (Math.random)
* 60,000 (timestamp)
-----------------------------
6,000,000,000,000,000,060,000
Це одиночний 6 000 000 000 000 000 060 000 померлих. Або, щоб зробити це число більш людським засвоюваним, це приблизно те саме число, що і
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
Звучить досить добре, правда? Що ж, давайте дізнаємось ...
SHA1 дає 20-байтне значення з можливими 256 ^ 20 результатами. Таким чином, ми дійсно не використовуємо SHA1 для повного потенціалу. Ну скільки ми використовуємо?
node> 6000000000000000060000 / Math.pow(256,20) * 100
Мілісекунда часової позначки та Math.random використовує лише 4,11e-27 відсотків 160-бітного потенціалу SHA1!
generator sha1 potential used
-----------------------------------------------------------------------------
crypto.randomBytes(20) 100%
Date() + Math.random() 0.00000000000000000000000000411%
6-sided die 0.000000000000000000000000000000000000000000000411%
A coin 0.000000000000000000000000000000000000000000000137%
Святі коти, чоловіче! Подивіться на всі ці нулі. Так наскільки краще crypto.randomBytes(20)
? 243,583,606,221,817,150,598,111,409 разів краще.
Примітки про +1
та частоту нулів
Якщо вам цікаво +1
, можливо, Math.random
повернути показник, 0
що означає ще 1 можливий унікальний результат, за який ми маємо враховувати.
Грунтуючись на обговоренні, яке відбулося нижче, мені було цікаво, наскільки часто 0
буде з'являтися. Ось невеликий сценарій random_zero.js
, я зробив, щоб отримати деякі дані
#!/usr/bin/env node
var count = 0;
while (Math.random() !== 0) count++;
console.log(count);
Потім я запустив його в 4 потоки (у мене 4-ядерний процесор), додавши висновок до файлу
$ yes | xargs -n 1 -P 4 node random_zero.js >> zeroes.txt
Тож виходить, що а 0
не так складно дістатись. Після того як було записано 100 значень , середнє значення було
1 з 31648548523 виплат становить 0
Класно! Більше дослідження необхідно буде знати , якщо це число на одному рівні з рівномірним розподілом v8 по Math.random
реалізації