ฉันใช้บรรทัดนี้เพื่อสร้างรหัส sha1 สำหรับ node.js:
crypto.createHash('sha1').digest('hex');
ปัญหาคือมันคืนรหัสเดียวกันทุกครั้ง
เป็นไปได้หรือไม่ที่จะสร้างรหัสสุ่มในแต่ละครั้งเพื่อให้ฉันสามารถใช้เป็นรหัสเอกสารฐานข้อมูลได้
ฉันใช้บรรทัดนี้เพื่อสร้างรหัส sha1 สำหรับ node.js:
crypto.createHash('sha1').digest('hex');
ปัญหาคือมันคืนรหัสเดียวกันทุกครั้ง
เป็นไปได้หรือไม่ที่จะสร้างรหัสสุ่มในแต่ละครั้งเพื่อให้ฉันสามารถใช้เป็นรหัสเอกสารฐานข้อมูลได้
คำตอบ:
ดูที่นี่: ฉันจะใช้ 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 อักขระ 20 ไบต์จะเป็นอักขระฐานสิบหก 40 ตัว
ใช้ 20 ไบต์เรามี256^20
หรือ1,461,501,637,330,902,918,203,684,832,716,2816,283,016,283,019,655,932,542,976ค่าการส่งออกที่ไม่ซ้ำกัน สิ่งนี้เหมือนกับเอาต์พุตที่เป็นไปได้ 160- บิต (20- ไบต์) ของ SHA1
เมื่อรู้สิ่งนี้มันไม่มีความหมายสำหรับเราที่จะshasum
สุ่มไบต์ของเรา มันเหมือนกับการกลิ้งตายสองครั้ง แต่ยอมรับการหมุนรอบที่สองเท่านั้น ไม่ว่าจะเกิดอะไรขึ้นคุณมี 6 ผลลัพธ์ที่เป็นไปได้ในแต่ละม้วนดังนั้นม้วนแรกก็เพียงพอ
ทำไมสิ่งนี้ถึงดีกว่า
เพื่อให้เข้าใจว่าทำไมสิ่งนี้ถึงดีกว่าก่อนอื่นเราต้องเข้าใจวิธีการทำงานของ hashing ฟังก์ชันการแฮช (รวมถึง SHA1) จะสร้างผลลัพธ์เดียวกันเสมอหากมีการป้อนข้อมูลเดียวกัน
สมมติว่าเราต้องการสร้าง ID แต่การป้อนข้อมูลแบบสุ่มของเราสร้างขึ้นโดยการโยนเหรียญ เรามี"heads"
หรือ"tails"
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
% echo -n "tails" | shasum
71ac9eed6a76a285ae035fe84a251d56ae9485a4 -
หาก"heads"
ขึ้นมาอีกครั้งเอาต์พุต SHA1 จะเหมือนเดิมเป็นครั้งแรก
% echo -n "heads" | shasum
c25dda249cdece9d908cc33adcd16aa05e20290f -
ตกลงดังนั้นการโยนเหรียญไม่ใช่เครื่องสร้างรหัสสุ่มที่ยอดเยี่ยมเพราะเรามีเพียง 2 เอาต์พุตที่เป็นไปได้
หากเราใช้แม่พิมพ์แบบ 6 ด้านมาตรฐานเรามี 6 อินพุตที่เป็นไปได้ ลองเดาว่ามีความเป็นไปได้ที่ SHA1 ส่งออกมากี่ตัว? 6!
input => (sha1) => output
1 => 356a192b7913b04c54574d18c28d46e6395428ab
2 => da4b9237bacccdf19c0760cab7aec4a8359010b0
3 => 77de68daecd823babbb58edb1c8e14d7106e83bb
4 => 1b6453892473a467d07372d45eb05abc2031647a
5 => ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4
6 => c1dfd96eea8cc2b62785275bca38ac261256e278
เราทั้งสองตกลงกันว่าการโยนเหรียญหรือตัวตายแบบ 6 ด้านจะทำให้เครื่องสร้างรหัสสุ่มไม่ถูกต้องเนื่องจากผลลัพธ์ SHA1 ที่เป็นไปได้ของเรา (ค่าที่เราใช้สำหรับ ID) มีน้อยมาก แต่ถ้าเราใช้บางอย่างที่มีเอาต์พุตมากกว่านั้นล่ะ ชอบเวลาประทับที่มีมิลลิวินาทีหรือไม่ หรือ JavaScript เป็นMath.random
? หรือแม้แต่การรวมกันของสองคนนี้!
ลองคำนวณจำนวนไอดีที่เราจะได้รับ ...
ความเป็นเอกลักษณ์ของเวลาประทับที่มีมิลลิวินาที
เมื่อใช้(new Date()).valueOf().toString()
งานคุณจะได้ตัวเลข 13 ตัว (เช่น1375369309741
) อย่างไรก็ตามเนื่องจากนี่เป็นหมายเลขการปรับปรุงตามลำดับ (หนึ่งครั้งต่อมิลลิวินาที) เอาต์พุตจึงเกือบจะเหมือนกันทุกครั้ง ลองมาดูกัน
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 อักขระ ผลลัพธ์ที่ยาวขึ้นหมายถึงตัวเลขที่มากกว่าซึ่งหมายถึงเอนโทรปีที่มากขึ้น ก่อนอื่นเราต้องหาว่าอันไหนที่มีความยาวน่าจะเป็นที่สุด
สคริปต์ด้านล่างจะกำหนดความยาวที่เป็นไปได้มากที่สุด เราทำสิ่งนี้โดยการสร้างตัวเลขสุ่ม 1 ล้านตัวและเพิ่มตัวนับตาม.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 });
โดยการหารแต่ละเคาน์เตอร์โดย 1 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.001ซ้ำ
ดังนั้นเราสามารถสร้างอินพุตสุ่มจำนวนเท่าใด
ตกลงเราคำนวณจำนวนผลลัพธ์สำหรับการประทับเวลามิลลิวินาทีและ 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 ชิ้นเดียว หรือเพื่อให้ตัวเลขนี้มากขึ้นอย่างมนุษย์ปุถุชนย่อยนี้เป็นประมาณจำนวนเดียวกับ
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 รายการที่เราต้องพิจารณา
จากการสนทนาที่เกิดขึ้นด้านล่างฉันสงสัยเกี่ยวกับความถี่ที่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 ค่าโดยเฉลี่ยแล้ว
1 ใน3,164,854,823 randoms คือ 0
เย็น! จะต้องมีการวิจัยเพิ่มเติมเพื่อทราบว่าหมายเลขดังกล่าวมีการกระจายMath.random
การดำเนินงานของ v8 อย่างสม่ำเสมอ
Date
น่ากลัวในการผลิตเมล็ดพันธุ์ที่ดี
Math.random
จะผลิต a0.
crypto.randomBytes
เป็นวิธีที่จะไปแน่นอน ^^
แก้ไข: นี่ไม่เหมาะกับการไหลของคำตอบก่อนหน้าของฉัน ฉันทิ้งไว้ที่นี่เป็นคำตอบที่สองสำหรับผู้ที่อาจมองหาวิธีนี้ในเบราว์เซอร์
คุณสามารถทำด้านลูกค้านี้ในเบราว์เซอร์ที่ทันสมัยถ้าคุณต้องการ
// 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)
= "5" ไม่ใช่ "05") ไม่สำคัญว่าคุณจะขึ้นอยู่กับผลลัพธ์สุดท้ายของคุณว่ามีความlen
ยาวอักขระอย่างแน่นอน ในกรณีนี้คุณสามารถใช้return ('0'+n.toString(16)).slice(-2);
ภายในฟังก์ชั่นแผนที่ของคุณ
id
แอตทริบิวต์ให้แน่ใจว่า ID เริ่มต้นด้วยตัวอักษร: [A-Za-z]