Как создать случайный хэш SHA1 для использования в качестве идентификатора в узле.Джей?
Я использую эту строку для создания идентификатора sha1 для узла.js:
crypto.createHash('sha1').digest('hex');
проблема в том, что он возвращает один и тот же идентификатор каждый раз.
можно ли каждый раз генерировать случайный идентификатор, чтобы я мог использовать его в качестве идентификатора документа базы данных?
3 ответов:
посмотреть здесь: Как я могу использовать узел.JS Crypto для создания хэша HMAC-SHA1? Я бы создал хэш текущей метки времени + случайное число, чтобы обеспечить уникальность хэша:
var current_date = (new Date()).valueOf().toString(); var random = Math.random().toString(); crypto.createHash('sha1').update(current_date + random).digest('hex');
243,583,606,221,817,150,598,111,409 x больше энтропии
я бы посоветовал использовать крипто.randomBytes. Это не
sha1, но для целей id, это быстрее, и так же, как "случайный".var id = crypto.randomBytes(20).toString('hex'); //=> f26d60305dae929ef8640a75e70dd78ab809cfe9результирующая строка будет в два раза длиннее случайных байтов, которые вы генерируете; каждый байт, закодированный в hex, состоит из 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или60000uniques.
уникальность
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 uniques.
Итак, сколько случайных входов мы можем генерировать?
хорошо, мы рассчитали количество результатов для миллисекундной метки времени и
Math.random100,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миллисекундная метка времени и математика.random использует только 4,11 e-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 возможный уникальный результат, который мы должны учитывать.основываясь на обсуждении, которое произошло ниже, мне было любопытно о частоте a
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 in 3,164,854,823 randoms - это 0
круто! Потребуется больше исследований, чтобы узнать, соответствует ли это число равномерному распределению v8
Math.randomреализация
сделать это в браузере, тоже !
EDIT: это действительно не вписывается в поток моего предыдущего ответа. Я оставляю его здесь в качестве второго ответа для людей, которые хотели бы сделать это в браузере.
вы можете сделать это на стороне клиента в современных браузерах, если вы хотите
// str byteToHex(uint8 byte) // converts a single byte to a hex string function byteToHex(byte) { return ('0' + byte.toString(16)).slice(-2); } // str generateId(int len); // len - must be an even number (default: 40) function generateId(len) { var arr = new Uint8Array((len || 40) / 2); window.crypto.getRandomValues(arr); return [].map.call(arr, byteToHex).join(""); }Хорошо, давайте проверим это !
generateId(); // "1e6ef8d5c851a3b5c5ad78f96dd086e4a77da800" generateId(20); // "d2180620d8f781178840"требования к браузеру
Browser Minimum Version -------------------------- Chrome 11.0 Firefox 21.0 IE 11.0 Opera 15.0 Safari 5.1
Comments