web-dev-qa-db-ja.com

node.jsでIDとして使用するランダムSHA1ハッシュを生成する方法は?

この行を使用して、node.jsのsha1 idを生成しています。

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

問題は、毎回同じIDを返すことです。

データベースドキュメントIDとして使用できるように、毎回ランダムなIDを生成することは可能ですか?

119
ajsie

ここをご覧ください: 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');
54
Gabi Purcaru

243,583,606,221,817,150,598,111,409xより多くのエントロピー

crypto.randomBytes を使用することをお勧めします。それはsha1ではありませんが、idの目的のために、より速く、ちょうど「ランダム」です。

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

結果の文字列は、生成するランダムバイトの2倍の長さになります。 16進数にエンコードされた各バイトは2文字です。 20バイトは16進数の40文字になります。

20バイトを使用すると、256^20または1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976固有の出力値が得られます。これはidenticalであり、SHA1の160ビット(20バイト)の可能な出力です。

これを知っているので、ランダムバイトをshasumすることはあまり意味がありません。サイコロを2回振るようなものですが、2番目のロールのみを受け入れます。何があっても、各ロールで6つの可能な結果があるので、最初のロールで十分です。


なぜこれが優れているのですか?

これがなぜ優れているのかを理解するには、まずハッシュ関数がどのように機能するかを理解する必要があります。ハッシュ関数(SHA1を含む)は、同じ入力が与えられると常に同じ出力を生成します。

IDを生成したいが、ランダムな入力はコイントスによって生成されたとします。 "heads"または"tails"があります

% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f  -

% echo -n "tails" | shasum
71ac9eed6a76a285ae035fe84a251d56ae9485a4  -

"heads"が再び表示される場合、SHA1の出力はsameになります

% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f  -

コイントスは、2つの可能な出力しかないため、優れたランダムIDジェネレーターではありません。

標準の6面ダイを使用する場合、6つの入力が可能です。可能なSHA1出力の数を推測しますか? 6!

input => (sha1) => output
1 => 356a192b7913b04c54574d18c28d46e6395428ab
2 => da4b9237bacccdf19c0760cab7aec4a8359010b0
3 => 77de68daecd823babbb58edb1c8e14d7106e83bb
4 => 1b6453892473a467d07372d45eb05abc2031647a
5 => ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4
6 => c1dfd96eea8cc2b62785275bca38ac261256e278

関数の出力がに見える非常にランダムで、非常にランダムであるという理由だけで、自分を欺くのは簡単です。

SHA1の結果(IDに使用する値)が非常に少ないため、コイントスまたは6面ダイスが悪いランダムIDジェネレーターを作成することに同意します。しかし、もっと多くの出力を持つものを使用するとどうなりますか?ミリ秒のタイムスタンプが好きですか?またはJavaScriptのMath.random?または、これらの2つの combination さえも!?

取得する一意のIDの数を計算してみましょう...


ミリ秒を含むタイムスタンプの一意性

(new Date()).valueOf().toString()を使用すると、13文字の数値(1375369309741など)が取得されます。ただし、これは順次更新される数値(1ミリ秒に1回)であるため、出力はほとんど常に同じです。見てみましょう

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

公平を期すために、比較のために、を1分間(寛大な操作実行時間)にすると、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,001uniquesが残ります。


では、ランダム入力をいくつ生成できますか?

OK、ミリ秒のタイムスタンプと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面のダイです。または、この数をより人間的に消化しやすくするために、これはroughlyと同じ数です

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 は、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.random0を返す可能性があります。これは、考慮する必要がある一意の結果がさらに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値 が記録された後、平均は

3,164,854,823の1ランダムは0

クール!その数がv8のMath.random実装の均一な分布と同等かどうかを知るには、さらに調査が必要です。

577
user633183

ブラウザでも実行してください!

編集:これは私の以前の答えの流れに実際には適合しませんでした。ブラウザーでこれを行うことを考えている人のための2番目の回答としてここに残します。

必要に応じて、最新のブラウザでこのクライアント側を実行できます

// 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
25
user633183