如何生成随机 SHA1散列作为 node.js 中的 ID?

我使用这一行来为 node.js 生成 sha1 id:

crypto.createHash('sha1').digest('hex');

问题是它每次都返回相同的 id。

有没有可能让它每次都生成一个随机标识,这样我就可以把它用作数据库文档标识了?

114154 次浏览

看看这里: 如何使用 node.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倍熵

我建议使用 加密,随机字节。它不是 sha1,但是为了标识的目的,它更快,而且是“随机的”。

var id = crypto.randomBytes(20).toString('hex');
//=> f26d60305dae929ef8640a75e70dd78ab809cfe9

结果字符串的长度将是随机字节的两倍; 编码为十六进制的每个字节为2个字符。20字节将是40个十六进制字符。

使用20字节,我们有 256^201,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*100060000的独特性。


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,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,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 只使用 SHA1160位潜能的4.11e-27% !

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是可能的,这意味着我们还要考虑另外一个可能的唯一结果。

基于下面发生的讨论,我对 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在 3,164,854,823随机是0

酷!需要更多的研究来了解这个数字是否与 v8的 Math.random实现的统一分布相当

在浏览器里也可以!

编辑: 这和我之前的回答不太一样。我把它留在这里,作为第二个答案,给那些可能想在浏览器中做这件事的人。

如果您愿意,可以在现代浏览器中完成这个客户端

// 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"

浏览器需求

Browser    Minimum Version
--------------------------
Chrome     11.0
Firefox    21.0
IE         11.0
Opera      15.0
Safari     5.1

使用 crypto是一种很好的方法,因为它是本地的、稳定的模块, 但是在有些情况下,如果您想创建一个真正强大和安全的散列,那么可以使用 bcrypt。我使用它的密码,它有很多技术散列,创建盐和比较密码。

技术1(在单独的函数调用上生成 salt 和 hash)

const salt = bcrypt.genSaltSync(saltRounds);
const hash = bcrypt.hashSync(myPlaintextPassword, salt);

技术2(自动生成一个 salt 和 hash) :

const hash = bcrypt.hashSync(myPlaintextPassword, saltRounds);

你可以在这里查看更多的例子: https://www.npmjs.com/package/bcrypt

如果要获取唯一标识符,应该使用 UUID (UUID)/GUID (全局唯一标识符)。

对于任何大小的输入,散列应该是确定的、唯一的、固定长度的。因此,无论运行散列函数多少次,如果使用相同的输入,输出将是相同的。

UUID 是唯一的并且是随机生成的! 有一个称为“ uuid”的包,您可以通过 npm 安装它

安装 uuid

& 在代码中导入模块

Const { v4: uuidv4} = 要求(‘ uuid’) ;

//调用方法 uuidv4或任何你命名的同时导入和记录它或存储它或分配它。该方法以字符串的形式返回 UUID。

Log (uuidv4()) ; //示例输出: “59594fc8-6a35-4f50-a966-4d735d8402ea”

下面是 npm 链接(如果你需要的话) : Https://www.npmjs.com/package/uuid