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


137

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

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

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

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


2
sha1は使用しないでください。安全ではありません(衝突防止)。これが、naomikの答えが優れている理由です。
Niels Abildgaard、2015

回答:


60

ここを見てください:node.js暗号を使用してHMAC-SHA1ハッシュを作成するにはどうすればよいですか? 現在のタイムスタンプ+乱数のハッシュを作成して、ハッシュの一意性を確保します。

var current_date = (new Date()).valueOf().toString();
var random = Math.random().toString();
crypto.createHash('sha1').update(current_date + random).digest('hex');

44
より優れたアプローチについては、以下の@naomikの回答を参照してください。
Gabi Purcaru 2013

2
これもGabiの優れた回答であり、ほんの少し速く、約15%でした。お疲れ様でした!私は実際にソルトでDate()を見るのが好きです。これにより、開発者は、これが非常識な並列コンピューティング状況を除いて、すべてがユニークな値になるという確信を簡単に得ることができます。私はそのばかげたものとrandomBytes(20)が一意になることを知っていますが、別のライブラリのランダム生成の内部に慣れていない可能性があるので、それは私たちにできる自信です。
Dmitri R117 2017

635

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バイトは40桁の16進数です。

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

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


なぜこれが良いのですか?

これがより良い理由を理解するには、まずハッシュ関数がどのように機能するかを理解する必要があります。同じ入力が指定された場合、ハッシュ関数(SHA1を含む)は常に同じ出力を生成します。

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

% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f  -

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

場合は"heads"再び来る、SHA1出力は次のようになります同じ、それが初めてでした

% 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つの組み合わせですか?

取得する一意の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

公平を期すために、比較の目的で、特定の分(寛大な操作実行時間)で、60*1000またはを60000一意にします。


の独自性 Math.random

Math.randomJavaScriptが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の一意が残ります。


では、ランダム入力をいくつ生成できるでしょうか?

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面のダイ1つです。または、この数をより人間が消化しやすいものにするために、これはほぼ次の数と同じです。

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ゼロの頻度と頻度に関する注記

について疑問に思っている場合は+1Math.randomを返す0ことが可能です。これは、考慮しなければならない可能性のある一意の結果がさらに1つあることを意味します。

以下で起こった議論に基づいて、私はa 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

したがって、a 0を取得するのはそれほど難しいことではないことがわかります。後に100個の値を記録し、平均でした

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

涼しい!その数がv8のMath.random実装の均一な分布と同等かどうかを知るには、さらに調査が必要になります


2
私のアップデートをご覧ください。ミリ秒でさえ、ライトスピードのjavascriptランドでは長い時間です。もっと深刻なことに、最初の10桁の数字は毎秒同じままです。これがDate良い種子を作るのに恐ろしいことです。
ありがとう

1
正しい。実際には、エントロピーの点で20のランダムバイトが優勢であることを示すために、他の回答に最も貢献するものだけを含めました。私は考えていないMath.random今までに生じるであろう0.
ありがとう

8
受け入れられた回答の14倍の賛成票...しかし、誰が数えていますか?:)
zx81

2
@moka、ダイスダイの複数形です。単数形を使用しています。
ありがとう

2
crypto.randomBytes間違いなく行く方法
です

28

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

編集:これは私の以前の答えの流れに実際には適合しませんでした。ブラウザでこれを実行しようとしている可能性のある人のための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

3
Number.toString(radix)2桁の値が常に保証されるわけではありません(例:(5).toString(16)「05」ではなく「5」)。最終出力が正確にlen文字数であることに依存しない限り、これは問題ではありません。この場合return ('0'+n.toString(16)).slice(-2);、マップ関数内で使用できます。
ブラウニーマン、

1
素晴らしいコード、ありがとう。追加したいのは、id属性の値に使用する場合は、IDが文字[A-Za-z]で始まることを確認してください。
GijsjanB

すばらしい回答(およびコメント)-ブラウザーの要件も回答に含めたことを本当に感謝しています!
ケブラー

ブラウザの要件が正しくありません。Array.from()はIE11 ではサポートされていません
プレフィックス

1
この回答の時点でwikiから取得されました。この回答は必要に応じて編集できますが、IEを本当に気にしているのは誰ですか。それをサポートしようとしている場合は、とにかくJavaScriptの半分をポリフィルする必要があります...
ありがとう
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.