この行を使用して、node.jsのsha1 idを生成しています。
crypto.createHash('sha1').digest('hex');
問題は、毎回同じIDを返すことです。
データベースドキュメントIDとして使用できるように、毎回ランダムなIDを生成することは可能ですか?
ここをご覧ください: 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');
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
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値 が記録された後、平均は
3,164,854,823の1ランダムは0
クール!その数がv8のMath.random
実装の均一な分布と同等かどうかを知るには、さらに調査が必要です。
編集:これは私の以前の答えの流れに実際には適合しませんでした。ブラウザーでこれを行うことを考えている人のための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