149

我正在使用这一行为 node.js 生成一个 sha1 id:

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

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

是否可以让它每次生成一个随机 ID,以便我可以将其用作数据库文档 ID?

4

5 回答 5

690

243,583,606,221,817,150,598,111,409 倍的熵

我建议使用crypto.randomBytes。不是sha1,但出于 id 的目的,它更快,并且就像“随机”一样。

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. 第一个字符总是0and .,所以实际上我们只得到 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 仅使用 SHA1 的 160 位潜力的 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返回 a 0,这意味着我们必须考虑另外 1 个可能的唯一结果。

根据下面发生的讨论,我很好奇 a0出现的频率。这是一个小脚本,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

所以事实证明,a0并不难获得。记录100 个值后,平均值为

3,164,854,823个随机数中有1个是 0

凉爽的!需要更多的研究来了解这个数字是否与 v8Math.random实现的均匀分布相当

于 2013-02-14T07:25:35.157 回答
67

看看这里:如何使用 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');
于 2012-02-23T06:28:57.887 回答
31

在浏览器中也可以!

编辑:这并不真正适合我之前回答的流程。对于可能希望在浏览器中执行此操作的人,我将其留在这里作为第二个答案。

如果您愿意,可以在现代浏览器中执行此客户端

// 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
于 2014-10-16T17:10:54.333 回答
0

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

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

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

npm 安装 uuid

& 在您的代码中导入模块

常量 { v4:uuidv4} = 要求('uuid');

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

控制台.log(uuidv4()); // 示例输出:'59594fc8-6a35-4f50-a966-4d735d8402ea'

这是 npm 链接(如果需要): https ://www.npmjs.com/package/uuid

于 2021-09-15T13:07:21.483 回答
0

Usingcrypto是一种很好的方法,因为它是本机且稳定的模块,但是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

于 2020-08-26T19:22:53.643 回答