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?
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?
Câu trả lời:
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');
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^20
hoặ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 shasum
cá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
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*1000
hoặ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 .length
mỗ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
và .
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
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ề +1
và tần số của số không
Nếu bạn đang tự hỏi về +1
điều đó, có thể Math.random
trả 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 0
sẽ 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 0
khô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.random
việc thực hiện v8 không
Date
khủng khiếp trong việc sản xuất hạt giống tốt.
Math.random
sẽ tạo ra một0.
crypto.randomBytes
chắc chắn là con đường để đi ^^
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
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ó độ len
dà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.
id
thuộc tính, hãy đảm bảo ID bắt đầu bằng một chữ cái: [A-Za-z].