如何在node.js中生成随机的SHA1哈希值作为ID?
我正在使用这一行来为node.js生成一个sha1标识符:
crypto.createHash('sha1').digest('hex');
问题是每次都返回相同的ID。
是否有可能让它每次生成一个随机的ID,所以我可以使用它作为数据库文档ID?
看看这里: 我如何使用node.jsencryption来创build一个HMAC-SHA1哈希? 我会创build一个散列的当前时间戳+一个随机数,以确保散列唯一性:
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,409x
我build议使用crypto.randomBytes 。 这不是sha1
,但是为了id的目的,它更快,就像“随机”一样。
var id = crypto.randomBytes(20).toString('hex'); //=> bb5dc8842ca31d4603d6aa11448d1654
结果string将是您生成的随机字节的两倍。 每个字节编码为hex是2个字符。 20个字节将是hex的40个字符。
使用20个字节,我们有256^20
或1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976个唯一的输出值。 这与 SHA1的160位(20字节)可能的输出相同。
知道这一点,我们shasum
我们的随机字节是没有意义的。 这就像滚动模具两次,但只接受第二轮; 不pipe怎么样,每个滚动你都有6个可能的结果,所以第一个滚动就足够了。
为什么这更好?
要理解为什么这样更好,我们首先必须了解哈希函数是如何工作的。 散列函数(包括SHA1)将始终生成相同的输出,如果给定相同的input。
假设我们想要生成ID,但是我们的随机input是由掷硬币产生的。 我们有"heads"
或"tails"
% echo -n "heads" | shasum c25dda249cdece9d908cc33adcd16aa05e20290f - % echo -n "tails" | shasum 71ac9eed6a76a285ae035fe84a251d56ae9485a4 -
如果"heads"
重新出现,SHA1输出将与第一次相同
% echo -n "heads" | shasum c25dda249cdece9d908cc33adcd16aa05e20290f -
好的,所以抛硬币并不是一个很好的随机ID生成器,因为我们只有2个可能的输出。
如果我们使用标准的6面模具,我们有6个可能的input。 猜猜有多less可能的SHA1输出? 6!
input => (sha1) => output 1 => 356a192b7913b04c54574d18c28d46e6395428ab 2 => da4b9237bacccdf19c0760cab7aec4a8359010b0 3 => 77de68daecd823babbb58edb1c8e14d7106e83bb 4 => 1b6453892473a467d07372d45eb05abc2031647a 5 => ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4 6 => c1dfd96eea8cc2b62785275bca38ac261256e278
只是因为我们的函数输出看起来非常随机,而且非常随机,所以很容易让我们自欺欺人。
我们都同意掷硬币或者六面模具会产生一个不好的随机id生成器,因为我们可能的SHA1结果(我们用于ID的值)很less。 但是如果我们用一些有更多输出的东西呢? 像一个毫秒的时间戳? 或JavaScript的Math.random
? 甚至是这两者的结合 ?
让我们计算一下我们会得到多less个独特的ID
时间戳的唯一性,以毫秒为单位
当使用(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个字符之间的数字。 更长的结果意味着更多的数字,这意味着更多的熵。 首先,我们需要找出最可能的长度。
下面的脚本将确定最可能的长度。 我们通过生成一百万个随机数字并基于每个数字的.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 });
通过将每个计数器除以100万,我们得到从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独特。
那么我们可以产生多less个随机input?
好的,我们计算了毫秒时间戳和Math.random
的结果数量
100,000,000,000,000,001 (Math.random) * 60,000 (timestamp) ----------------------------- 6,000,000,000,000,000,060,000
这是一个单一的6000000000000000000双面模具。 或者,为了使这个数字更人性易消化,这个数字与数字大致相同
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来充分发挥它的潜力。 那么我们使用多less?
node> 6000000000000000060000 / Math.pow(256,20) * 100
毫秒时间戳和Math.random只使用4.11e-27%的SHA1的160位潜力!
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
所以事实certificate,一个0
不是很难得到。 logging了100个值之后,平均值为
在3,164,854,823个随机中是1是0
凉! 需要更多的研究来了解这个数字是否与v8的Math.random
实现的统一分布一致
在浏览器中也是这样!
编辑:这不适合我以前的答案的stream程。 我将把它留在这里作为第二个答案,可能是在浏览器中寻找的人。
如果你愿意,你可以在现代浏览器中做这个客户端
// 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