回答:
ここを見てください: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');
crypto.randomBytesの使用をお勧めします。それはそうではありませんsha1
が、idの目的では、より速く、「ランダム」と同じです。
var id = crypto.randomBytes(20).toString('hex');
//=> f26d60305dae929ef8640a75e70dd78ab809cfe9
結果の文字列は、生成したランダムバイトの2倍の長さになります。16進数にエンコードされた各バイトは2文字です。20バイトは40桁の16進数です。
20バイトを使用すると256^20
、1,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.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
and .
であるため、実際にはランダムな文字は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
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つあることを意味します。
以下で起こった議論に基づいて、私は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
実装の均一な分布と同等かどうかを知るには、さらに調査が必要になります
Date
良い種子を作るのに恐ろしいことです。
Math.random
今までに生じるであろう0.
crypto.randomBytes
間違いなく行く方法
編集:これは私の以前の答えの流れに実際には適合しませんでした。ブラウザでこれを実行しようとしている可能性のある人のための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
Number.toString(radix)
2桁の値が常に保証されるわけではありません(例:(5).toString(16)
「05」ではなく「5」)。最終出力が正確にlen
文字数であることに依存しない限り、これは問題ではありません。この場合return ('0'+n.toString(16)).slice(-2);
、マップ関数内で使用できます。
id
属性の値に使用する場合は、IDが文字[A-Za-z]で始まることを確認してください。