我正在使用此行为node.js生成sha1 id:
crypto.createHash('sha1').digest('hex');
问题在于它每次都返回相同的ID。
是否可以让它每次生成一个随机ID,以便我可以将它用作数据库文档ID?
答案 0 :(得分:567)
我建议使用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 唯一输出值。这与SHA1的160位(20字节)可能输出相同。
了解这一点,shasum
我们的随机字节对我们来说并没有多大意义。这就像滚动模具两次但只接受第二次滚动;无论如何,每卷都有6种可能的结果,所以第一次滚动就足够了。
为什么这样更好?
要理解为什么这样做更好,我们首先要了解散列函数的工作原理。如果给出相同的输入,散列函数(包括SHA1)将始终生成相同的输出。
假设我们想要生成ID,但我们的随机输入是通过抛硬币生成的。我们有"heads"
或"tails"
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
% echo -n "tails" | shasum
71ac9eed6a76a285ae035fe84a251d56ae9485a4 -
如果"heads"
再次出现,SHA1输出将是第一次相同
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
好的,所以掷硬币不是一个很好的随机ID生成器,因为我们只有2个可能的输出。
如果我们使用标准的6面模具,我们有6种可能的输入。猜猜有多少可能的SHA1输出? 6!
input => (sha1) => output
1 => 356a192b7913b04c54574d18c28d46e6395428ab
2 => da4b9237bacccdf19c0760cab7aec4a8359010b0
3 => 77de68daecd823babbb58edb1c8e14d7106e83bb
4 => 1b6453892473a467d07372d45eb05abc2031647a
5 => ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4
6 => c1dfd96eea8cc2b62785275bca38ac261256e278
我们都同意硬币抛掷或6面骰子会产生一个糟糕的随机id生成器,因为我们可能的SHA1结果(我们用于ID的值)非常少。但是,如果我们使用具有更多输出的东西呢?就像一个毫秒的时间戳?或者JavaScript的Math.random
?或者甚至是那两个的组合?!
让我们计算一下我们会得到多少独特的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个字符之间的数字。更长的结果意味着更多的数字意味着更多的熵。首先,我们需要找出最可能的长度。
下面的脚本将确定最可能的长度。我们通过生成100万个随机数并根据每个数字的.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 唯一身份。
那么我们可以生成多少随机输入?
好的,我们计算了毫秒时间戳和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
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 values后,平均值为
3,164,854,823 randoms中的为0
酷!需要更多的研究来了解这个数字是否与v8 Math.random
实施的统一分布相同
答案 1 :(得分:51)
看看这里:How do I use node.js Crypto to create a HMAC-SHA1 hash? 我将创建当前时间戳的哈希值+随机数以确保哈希唯一性:
var current_date = (new Date()).valueOf().toString();
var random = Math.random().toString();
crypto.createHash('sha1').update(current_date + random).digest('hex');
答案 2 :(得分:25)
编辑:这并不适合我以前的答案。我将其留在这里作为可能希望在浏览器中执行此操作的人的第二个答案。
如果您喜欢
,可以在现代浏览器中使用此客户端
// 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 = 40) {
var arr = new Uint8Array(len / 2);
window.crypto.getRandomValues(arr);
return Array.from(arr, byteToHex).join("");
}
console.log(generateId())
// "1e6ef8d5c851a3b5c5ad78f96dd086e4a77da800"
console.log(generateId(20))
// "d2180620d8f781178840"
&#13;
浏览器要求
Browser Minimum Version
--------------------------
Chrome 11.0
Firefox 21.0
IE 11.0
Opera 15.0
Safari 5.1
答案 3 :(得分:0)
使用crypto
是一个很好的方法,因为它是本机且稳定的模块,
但是在某些情况下,如果您想创建一个真正强大且安全的哈希,可以使用bcrypt
。我将它用作密码,它具有很多用于散列,创建盐和比较密码的技术。
技术1(在单独的函数调用上生成盐和哈希)
const salt = bcrypt.genSaltSync(saltRounds);
const hash = bcrypt.hashSync(myPlaintextPassword, salt);
技术2(自动生成盐和哈希值):
const hash = bcrypt.hashSync(myPlaintextPassword, saltRounds);
有关更多示例,您可以在此处查看:https://www.npmjs.com/package/bcrypt