Node.js'de kimlik olarak kullanmak için rasgele SHA1 karması nasıl oluşturulur?


137

Bu satırı node.js için bir sha1 kimliği oluşturmak için kullanıyorum:

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

Sorun, her seferinde aynı kimliği döndürmesi.

Bir veritabanı belge kimliği olarak kullanabilmek için her seferinde rastgele bir kimlik oluşturmak mümkün mü?


2
Sha1 kullanmayın. Artık güvenli kabul edilmemektedir (çarpışmalara dayanıklı). Bu yüzden naomik'in cevabı daha iyidir.
Niels Abildgaard

Yanıtlar:


60

Buraya bir göz atın: Bir HMAC-SHA1 karması oluşturmak için node.js Kripto parasını nasıl kullanabilirim? Karma benzersizliği sağlamak için geçerli zaman damgasının bir karma + rastgele bir sayı oluşturmak istiyorum:

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

44
Daha iyi bir yaklaşım için aşağıdaki @ naomik'in cevabına bakınız.
Gabi Purcaru

2
Bu aynı zamanda büyük bir cevap Gabi ve sadece biraz daha hızlı, yaklaşık% 15 idi. Her ikisi de harika iş! Aslında tuzda bir Date () görmeyi seviyorum, bir geliştiriciye bunun en çılgın paralel bilgi işlem durumları dışında benzersiz bir değer olacağına dair kolay bir güven veriyor. Aptal ve rastgeleBytes (20) benzersiz olacak biliyorum, ama sadece bir güven olabilir çünkü başka bir kütüphanenin rastgele nesil içlerine aşina olmayabilir.
Dmitri R117

637

243.583.606.221.817.150.598.111.409x daha fazla entropi

Crypto.randomBytes kullanmanızı öneririm . Değil sha1, ama kimlik amaçları için, daha hızlı ve "rastgele" gibi.

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

Ortaya çıkan dize, oluşturduğunuz rasgele baytların iki katı kadar uzun olacaktır; onaltılık olarak kodlanan her bayt 2 karakterdir. 20 bayt, onaltılık 40 karakter olacaktır.

20 bayt kullanarak, var 256^20ya 1.461.501.637.330.902.918.203.684.832.716.283.019.655.932.542.976 benzersiz çıkış değerleri. Bu, SHA1'in 160 bit (20 bayt) olası çıkışları ile aynıdır .

Bunu bilmek, shasumrastgele baytlarımız için gerçekten anlamlı değil . Bir kalıbı iki kez yuvarlamak gibi ama sadece ikinci ruloyu kabul etmek gibi; Ne olursa olsun, her bir ruloda 6 olası sonuç vardır, bu nedenle ilk rulo yeterlidir.


Bu neden daha iyi?

Bunun neden daha iyi olduğunu anlamak için, öncelikle hash fonksiyonlarının nasıl çalıştığını anlamalıyız. Aynı giriş verilirse karma işlevleri (SHA1 dahil) her zaman aynı çıktıyı üretir.

Kimlik oluşturmak istediğimizi, ancak rastgele girdimizin bir bozuk para atma tarafından oluşturulduğunu varsayalım. Sahip olduğumuz "heads"veya"tails"

% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f  -

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

Eğer "heads"tekrar çıkageldi, SHA1 çıkışı olacaktır aynı o ilk kez olarak

% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f  -

Tamam, bu yüzden sadece 2 olası çıkışımız var.

Standart 6 taraflı bir kalıp kullanırsak, 6 olası girişimiz olur. Kaç olası SHA1 çıkışı tahmin edin? 6!

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

Bu sırf bizim fonksiyonu çıkış düşünerek kendimizi kandırma kolay görünüyor o, çok tesadüfi olduğu çok rastgele.

İkimiz de bir bozuk para atma veya 6 taraflı bir kalıbın kötü bir rasgele kimlik üreteci yapacağına katılıyoruz, çünkü olası SHA1 sonuçlarımız (kimlik için kullandığımız değer) çok az. Peki ya çok daha fazla çıktısı olan bir şey kullanırsak? Milisaniyelik bir zaman damgası gibi mi? Veya JavaScript Math.random? Ya da bu ikisinin bir kombinasyonu mu ?!

Kaç tane benzersiz id alacağımızı hesaplayalım ...


Milisaniye ile bir zaman damgasının benzersizliği

Kullanırken (new Date()).valueOf().toString()13 karakterlik bir sayı alırsınız (ör 1375369309741.). Bununla birlikte, bu ardışık olarak güncellenen bir sayı (milisaniye başına bir kez) olduğundan, çıktılar neredeyse her zaman aynıdır. Hadi bir bakalım

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

Adil olmak gerekirse, karşılaştırma amacıyla, belirli bir dakika içinde (cömert bir operasyon yürütme süresi), sahip olacaksınız 60*1000veya 60000benzersiz olacaksınız .


Benzersizliği Math.random

Şimdi, kullanırken Math.random, JavaScript'in 64 bit kayan nokta sayılarını temsil etme şekli nedeniyle, 13 ila 24 karakter uzunluğunda bir uzunlukta bir sayı alırsınız. Daha uzun bir sonuç daha fazla hane anlamına gelir, bu da daha fazla entropi anlamına gelir. İlk olarak, hangisinin en olası uzunluk olduğunu bulmamız gerekir.

Aşağıdaki komut dosyası hangi uzunluğun en olası olduğunu belirleyecektir. Bunu 1 milyon rasgele sayı üreterek ve .lengthher sayıya göre bir sayaç artırarak yaparız .

// 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 });

Her sayacı 1 milyona bölerek, geri dönen sayının uzunluğu olasılığını elde ederiz 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

Tamamen doğru olmasa da, cömert olalım ve 19 karakter uzunluğunda rastgele bir çıktı elde edeceğinizi varsayalım; 0.1234567890123456789. İlk karakterler her zaman olacak 0ve .bu yüzden gerçekten sadece 17 rastgele karakter alıyoruz. Bu bizi 10^17 +1(olası 0; aşağıdaki notlara bakın) veya 100.000.000.000.000.001 benzersiz bırakır .


Peki kaç rastgele girdi üretebiliriz?

Tamam, milisaniye zaman damgası için sonuç sayısını hesapladık ve Math.random

      100,000,000,000,000,001 (Math.random)
*                      60,000 (timestamp)
-----------------------------
6,000,000,000,000,000,060,000

Bu 6.000.000.000.000.000.060.000 taraflı tek bir kalıp. Veya bu sayıyı insanca daha sindirilebilir hale getirmek için, bu rakam kabaca aynı sayıdır.

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

Kulağa hoş geliyor, değil mi? Hadi öğrenelim ...

SHA1 , olası 256 ^ 20 sonuçlarla 20 baytlık bir değer üretir. Bu yüzden SHA1'i tam potansiyeline kullanmıyoruz. Peki ne kadar kullanıyoruz?

node> 6000000000000000060000 / Math.pow(256,20) * 100

Milisaniyelik bir zaman damgası ve Math.random, SHA1'in 160 bit potansiyelinin sadece yüzde 4.11e-27'sini kullanıyor!

generator               sha1 potential used
-----------------------------------------------------------------------------
crypto.randomBytes(20)  100%
Date() + Math.random()    0.00000000000000000000000000411%
6-sided die               0.000000000000000000000000000000000000000000000411%
A coin                    0.000000000000000000000000000000000000000000000137%

Kutsal kediler dostum! Şu sıfırlara bak. Peki ne kadar iyi crypto.randomBytes(20)? 243.583.606.221.817.150.598.111.409 kat daha iyi.


+1Sıfır ve sıklığı hakkında notlar

Eğer merak ediyorsanız +1, Math.randomgeri dönmek mümkündür, bu 0da hesaba katmamız gereken 1 olası benzersiz sonuç daha var demektir.

Aşağıda olan tartışmaya dayanarak, a'nın ortaya çıkma sıklığını merak ettim 0. İşte küçük bir senaryo, random_zero.jsbazı veriler elde ettim

#!/usr/bin/env node
var count = 0;
while (Math.random() !== 0) count++;
console.log(count);

Sonra, 4 iş parçacığında çalıştırdım (4 çekirdekli işlemcim var), çıktıyı bir dosyaya ekliyorum

$ yes | xargs -n 1 -P 4 node random_zero.js >> zeroes.txt

Yani a'nın 0elde edilmesi o kadar zor değil. Sonra 100 değerleri kaydedildi, ortalama

3.164.854.823 rastgele 1 0

Güzel! Bu sayının eşit v8 Math.randomuygulamasının dağılımı ile eşit olup olmadığını bilmek için daha fazla araştırma yapılması gerekecektir.


2
Lütfen güncellememe bakın; milisaniyede bile ışık hızı javascript topraklarında uzun bir süre var! Daha ciddi bir notta, sayının ilk 10 hanesi her saniye aynı kalır; Dateiyi tohumlar üretmeyi berbat eden şey budur .
Teşekkürler

1
Doğru. Her ne kadar 20 rasgele baytın sadece entropi açısından hâkim olduğunu göstermek için diğer cevaba en yüksek katkıyı verecekleri dahil etsem de. Math.randomHiç bir zaman üreteceğini sanmıyorum0.
Teşekkürler

8
Kabul edilen cevaptan 14 kat daha fazla upvotes ... ama kim sayıyor? :)
zx81

2
moka, zar ölmenin çoğul halidir . Tekil formu kullanıyorum.
Teşekkürler

2
crypto.randomByteskesinlikle gitmek için yol ^ ^
Teşekkür ederim

28

Tarayıcıda da yapın!

EDIT: Bu benim önceki cevabımın akışına gerçekten uymadı. Bunu, tarayıcıda bunu yapmak isteyebilecek insanlar için ikinci bir cevap olarak bırakıyorum.

İsterseniz bu istemci tarafını modern tarayıcılarda yapabilirsiniz

// 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"

Tarayıcı gereksinimleri

Browser    Minimum Version
--------------------------
Chrome     11.0
Firefox    21.0
IE         11.0
Opera      15.0
Safari     5.1

3
Number.toString(radix)her zaman 2 basamaklı bir değer garanti etmez (ör: (5).toString(16)"05" değil, " : " 5 "). Son çıktınızın tam olarak lenkarakter uzunluğuna bağlı olmadıkça bu önemli değildir . Bu durumda return ('0'+n.toString(16)).slice(-2);harita fonksiyonunuzun içinde kullanabilirsiniz .
Kaslı Adam

1
Harika kod, teşekkürler. Sadece şunu eklemek istedim: bir idniteliğin değeri için kullanacaksanız , kimliğin bir harfle başladığından emin olun: [A-Za-z].
GijsjanB

Harika yanıt (ve yorumlar) - yanıta tarayıcı gereksinimlerini de eklediğinizi gerçekten takdir ettim!
kevlarr

Tarayıcı gereksinimleri yanlış. Array.from () olduğu değil IE11 içinde destekledi.
Ön ek

1
Bu cevap anında bir wikiden alınmıştır. İsterseniz bu yanıtı düzenleyebilirsiniz, ancak IE'yi gerçekten kimin umurunda? Bunu desteklemeye çalışıyorsanız, JavaScript'in yarısını yine de doldurmanız gerekiyor ...
Teşekkürler
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.