Làm cách nào để tạo băm SHA1 ngẫu nhiên để sử dụng làm ID trong tệp node.js?


137

Tôi đang sử dụng dòng này để tạo id sha1 cho node.js:

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

Vấn đề là nó trở lại cùng một id mỗi lần.

Có thể để nó tạo một id ngẫu nhiên mỗi lần để tôi có thể sử dụng nó làm id tài liệu cơ sở dữ liệu không?


2
Đừng sử dụng sha1. Nó không còn được coi là an toàn (chống va chạm). Đây là lý do tại sao câu trả lời của naomik là tốt hơn.
Niels Abildgaard

Câu trả lời:


60

Có một cái nhìn ở đây: Làm cách nào để tôi sử dụng node.js Crypto để tạo băm HMAC-SHA1? Tôi sẽ tạo một hàm băm của dấu thời gian hiện tại + một số ngẫu nhiên để đảm bảo tính duy nhất của hàm băm:

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

44
Để có cách tiếp cận tốt hơn nhiều, hãy xem câu trả lời của @ naomik bên dưới.
Gabi Purcaru

2
Đây cũng là một câu trả lời tuyệt vời của Gabi, và chỉ nhanh hơn một chút, khoảng 15%. Cả nhà làm tốt lắm! Tôi thực sự muốn thấy một Ngày () trong muối, nó giúp nhà phát triển dễ dàng tự tin rằng đây sẽ là giá trị duy nhất trong tất cả các tình huống trừ tính toán song song điên rồ nhất. Tôi biết sự ngớ ngẩn và RandomBytes (20) của nó sẽ là duy nhất, nhưng đó chỉ là sự tự tin mà chúng ta có thể có vì chúng ta có thể không quen thuộc với các thế hệ ngẫu nhiên của một thư viện khác.
Dmitri R117

635

243,583.606.221,817,150,598,11,409x entropy nhiều hơn

Tôi khuyên bạn nên sử dụng crypto.randomBytes . Không phải sha1, nhưng với mục đích id, nó nhanh hơn và chỉ là "ngẫu nhiên".

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

Chuỗi kết quả sẽ dài gấp đôi số byte ngẫu nhiên mà bạn tạo ra; mỗi byte được mã hóa thành hex là 2 ký tự. 20 byte sẽ là 40 ký tự hex.

Sử dụng 20 byte, chúng ta có 256^20hoặc 1.461.501.637.330,902,918,203,684,832,716,283,019,655,932,542,976 giá trị đầu ra duy nhất. Điều này giống hệt với các đầu ra có thể 160 bit (20 byte) của SHA1.

Biết điều này, nó không thực sự có ý nghĩa đối với shasumcác byte ngẫu nhiên của chúng tôi. Nó giống như lăn một cái chết hai lần nhưng chỉ chấp nhận cuộn thứ hai; không có vấn đề gì, bạn có 6 kết quả có thể có cho mỗi cuộn, vì vậy cuộn đầu tiên là đủ.


Tại sao điều này tốt hơn?

Để hiểu tại sao điều này tốt hơn, trước tiên chúng ta phải hiểu cách các hàm băm hoạt động. Các hàm băm (bao gồm SHA1) sẽ luôn tạo ra cùng một đầu ra nếu cùng một đầu vào được đưa ra.

Giả sử chúng tôi muốn tạo ID nhưng đầu vào ngẫu nhiên của chúng tôi được tạo bằng cách tung đồng xu. Chúng tôi có "heads"hoặc"tails"

% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f  -

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

Nếu "heads"xuất hiện trở lại, đầu ra SHA1 sẽ giống như lần đầu tiên

% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f  -

Ok, do đó, việc tung đồng xu không phải là một trình tạo ID ngẫu nhiên tuyệt vời vì chúng tôi chỉ có 2 đầu ra có thể.

Nếu chúng ta sử dụng khuôn 6 mặt tiêu chuẩn, chúng ta có 6 đầu vào có thể. Đoán có bao nhiêu đầu ra SHA1 có thể? 6!

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

Thật dễ dàng để lừa dối mình bằng cách nghĩ chỉ vì đầu ra của chức năng của chúng tôi vẻ rất ngẫu nhiên, rằng nó rất ngẫu nhiên.

Cả hai chúng tôi đều đồng ý rằng việc tung đồng xu hoặc chết 6 mặt sẽ tạo ra một trình tạo id ngẫu nhiên xấu, bởi vì kết quả SHA1 có thể có của chúng tôi (giá trị chúng tôi sử dụng cho ID) là rất ít. Nhưng nếu chúng ta sử dụng thứ gì đó có đầu ra nhiều hơn thì sao? Giống như dấu thời gian với mili giây? Hay JavaScript Math.random? Hay thậm chí là sự kết hợp của cả hai?!

Hãy tính xem có bao nhiêu id duy nhất chúng ta sẽ nhận được ...


Sự độc đáo của dấu thời gian với mili giây

Khi sử dụng (new Date()).valueOf().toString(), bạn sẽ nhận được một số gồm 13 ký tự (ví dụ 1375369309741:). Tuy nhiên, vì đây là số cập nhật liên tục (một lần trên mili giây), nên các đầu ra hầu như luôn giống nhau. Chúng ta hãy xem

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

Để công bằng, với mục đích so sánh, trong một phút nhất định (thời gian thực hiện thao tác hào phóng), bạn sẽ có 60*1000hoặc bỏ qua 60000.


Sự độc đáo của Math.random

Bây giờ, khi sử dụng Math.random, do cách JavaScript biểu thị các số dấu phẩy động 64 bit, bạn sẽ nhận được một số có độ dài ở bất kỳ đâu dài từ 13 đến 24 ký tự. Một kết quả dài hơn có nghĩa là nhiều chữ số hơn có nghĩa là nhiều entropy hơn. Đầu tiên, chúng ta cần tìm ra chiều dài có thể xảy ra nhất.

Kịch bản dưới đây sẽ xác định độ dài nào có thể xảy ra nhất. Chúng tôi thực hiện điều này bằng cách tạo 1 triệu số ngẫu nhiên và tăng bộ đếm dựa trên .lengthmỗi số.

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

Bằng cách chia mỗi bộ đếm cho 1 triệu, chúng ta có được xác suất về độ dài của số được trả về 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

Vì vậy, mặc dù điều đó không hoàn toàn đúng, nhưng hãy hào phóng và nói rằng bạn nhận được đầu ra ngẫu nhiên dài 19 ký tự; 0.1234567890123456789. Các ký tự đầu tiên sẽ luôn như vậy, 0.thực sự chúng ta chỉ nhận được 17 ký tự ngẫu nhiên. Điều này để lại cho chúng tôi 10^17 +1(để có thể 0; xem ghi chú bên dưới) hoặc 100.000.000.000.001 đơn vị .


Vậy chúng ta có thể tạo ra bao nhiêu đầu vào ngẫu nhiên?

Ok, chúng tôi đã tính số lượng kết quả cho dấu thời gian một phần nghìn giây và Math.random

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

Đó là một cái chết 6.000.000.000.000.000.060.000 mặt. Hoặc, để tận con số này mà con người tiêu hóa hơn, đây là khoảng cùng một số như

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

Âm thanh khá tốt, phải không? Chà, hãy tìm hiểu ...

SHA1 tạo ra giá trị 20 byte, với kết quả 256 ^ 20 có thể. Vì vậy, chúng tôi thực sự không sử dụng SHA1 cho tiềm năng đầy đủ của nó. Chúng ta đang sử dụng bao nhiêu?

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

Dấu thời gian một phần nghìn giây và Math.random chỉ sử dụng 4,11e-27 phần trăm tiềm năng 160 bit của SHA1!

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

Mèo thần thánh! Nhìn vào tất cả những con số không. Vậy tốt hơn bao nhiêu crypto.randomBytes(20)? 243,583.606.221,817,150,598,11,409 lần tốt hơn.


Ghi chú về +1và tần số của số không

Nếu bạn đang tự hỏi về +1điều đó, có thể Math.randomtrả lại một 0điều có nghĩa là chúng ta phải tính đến 1 kết quả duy nhất có thể có.

Dựa trên cuộc thảo luận đã xảy ra dưới đây, tôi tò mò về tần suất a 0sẽ xuất hiện. Đây là một kịch bản nhỏ random_zero.js, tôi đã thực hiện để có được một số dữ liệu

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

Sau đó, tôi chạy nó trong 4 luồng (tôi có bộ xử lý 4 lõi), nối thêm đầu ra vào một tệp

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

Vì vậy, nó chỉ ra rằng 0không khó để có được. Sau khi 100 giá trị được ghi lại, trung bình là

1 trong 3.164.854.823 randoms là 0

Mát mẻ! Cần có thêm nhiều nghiên cứu để biết liệu con số đó có ngang bằng với phân phối thống nhất của Math.randomviệc thực hiện v8 không


2
Xin vui lòng xem cập nhật của tôi; thậm chí một phần nghìn giây là một khoảng thời gian dài trên vùng đất javascript ánh sáng! Một lưu ý nghiêm trọng hơn, 10 chữ số đầu tiên của số vẫn giữ nguyên mỗi giây; đây là những gì làm cho Datekhủng khiếp trong việc sản xuất hạt giống tốt.
Cảm ơn bạn

1
Chính xác. Mặc dù tôi thực sự chỉ bao gồm những người đóng góp cao nhất cho câu trả lời khác để chứng minh rằng 20 byte ngẫu nhiên vẫn chỉ chiếm ưu thế về mặt entropy. Tôi không nghĩ Math.randomsẽ tạo ra một0.
Cảm ơn bạn

8
Tăng gấp 14 lần so với câu trả lời được chấp nhận ... nhưng ai đang đếm? :)
zx81

2
@moka, súc sắc là dạng số nhiều của die . Tôi đang sử dụng hình thức số ít.
Cảm ơn bạn

2
crypto.randomByteschắc chắn là con đường để đi ^^
Cảm ơn bạn

28

Làm điều đó trong trình duyệt, quá!

EDIT: điều này không thực sự phù hợp với dòng câu trả lời trước của tôi. Tôi để nó ở đây như một câu trả lời thứ hai cho những người có thể đang tìm cách làm điều này trong trình duyệt.

Bạn có thể thực hiện phía máy khách này trong các trình duyệt hiện đại, nếu bạn muốn

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

Yêu cầu trình duyệt

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

3
Number.toString(radix)không phải lúc nào cũng đảm bảo giá trị 2 chữ số (ví dụ: (5).toString(16)= "5", không phải "05"). Điều này không quan trọng trừ khi bạn phụ thuộc vào đầu ra cuối cùng của mình để có độ lendài chính xác của các ký tự. Trong trường hợp này, bạn có thể sử dụng return ('0'+n.toString(16)).slice(-2);bên trong chức năng bản đồ của mình.
The Brawny Man

1
Mã tuyệt vời, cảm ơn. Chỉ muốn thêm: nếu bạn sẽ sử dụng nó cho giá trị của một idthuộc tính, hãy đảm bảo ID bắt đầu bằng một chữ cái: [A-Za-z].
GijsjanB

Câu trả lời tuyệt vời (và nhận xét) - thực sự đánh giá cao rằng bạn cũng bao gồm các yêu cầu của trình duyệt trong câu trả lời!
kevlarr

Các yêu cầu trình duyệt không chính xác. Array.from () không được hỗ trợ trong IE11.
Tiền tố

1
Nó được lấy từ một wiki tại thời điểm trả lời này. Bạn có thể chỉnh sửa câu trả lời này nếu bạn muốn, nhưng ai thực sự quan tâm đến IE? Nếu bạn đang cố gắng hỗ trợ nó, dù sao bạn cũng phải hoàn thành một nửa JavaScript ...
Cảm ơn bạn vào
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.