ความแตกต่างระหว่าง java.util.Random และ java.security.SecureRandom


202

ทีมของฉันได้มอบรหัสฝั่งเซิร์ฟเวอร์ (ใน Java) ที่สร้างโทเค็นแบบสุ่มและฉันมีคำถามเกี่ยวกับสิ่งเดียวกัน -

วัตถุประสงค์ของโทเค็นเหล่านี้มีความอ่อนไหวพอสมควร - ใช้สำหรับรหัสเซสชันลิงก์การตั้งรหัสผ่านเป็นต้นดังนั้นพวกเขาจึงจำเป็นต้องมีการสุ่มเข้ารหัสเพื่อหลีกเลี่ยงใครบางคนที่เดาพวกเขา โทเค็นคือ "ยาว" ดังนั้นจึงมีความยาว 64 บิต

รหัสปัจจุบันใช้java.util.Randomคลาสเพื่อสร้างโทเค็นเหล่านี้ เอกสารสำหรับการjava.util.Randomระบุอย่างชัดเจนต่อไปนี้:

อินสแตนซ์ของ java.util.Random ไม่ปลอดภัยแบบเข้ารหัส พิจารณาใช้ SecureRandom แทนเพื่อให้เครื่องสร้างตัวเลขสุ่มหลอกเข้ารหัสลับปลอดภัยสำหรับใช้โดยแอปพลิเคชันที่มีความปลอดภัย

อย่างไรก็ตามวิธีการที่ใช้รหัสในปัจจุบันjava.util.Randomคือ - มันทำให้คลาสเป็นอินสแตนซ์java.security.SecureRandomแล้วใช้SecureRandom.nextLong()วิธีการรับเมล็ดที่ใช้สำหรับอินสแตนซ์java.util.Randomชั้น จากนั้นจะใช้java.util.Random.nextLong()วิธีการสร้างโทเค็น

ดังนั้นคำถามของฉันตอนนี้ - มันยังไม่ปลอดภัยหรือไม่เมื่อjava.util.Randomใช้เมล็ดjava.security.SecureRandom? ฉันจำเป็นต้องแก้ไขรหัสเพื่อใช้เป็นjava.security.SecureRandomเอกสิทธิ์ในการสร้างโทเค็นหรือไม่

ในปัจจุบันเมล็ดรหัสนั้นเป็นRandomครั้งเดียวเมื่อเริ่มต้น


14
เมื่อ seeded เอาต์พุตจาก java.util.Random จะเป็นลำดับตัวเลขที่กำหนดไว้ คุณอาจไม่ต้องการที่
ปีเตอร์ibrtibraný

1
รหัสRandomเริ่มต้นครั้งเดียวหรือไม่หรือเป็นรหัสใหม่สำหรับทุกโทเค็นหรือไม่ หวังว่านี่เป็นคำถามที่โง่ แต่ฉันคิดว่าฉันจะตรวจสอบ
Tom Anderson

8
Random มีสถานะภายใน 48 บิตเท่านั้นและจะทำซ้ำหลังจากการโทร 2 ^ 48 ไปที่ nextLong () ซึ่งหมายความว่าจะไม่ทำให้เกิดความเป็นไปได้longหรือdoubleค่าทั้งหมด
Peter Lawrey

3
มีปัญหารุนแรงอีกอย่างหนึ่งคือ 64 บิตหมายถึงการรวมกันที่เป็นไปได้ 1.84 * 10 ^ 19 ซึ่งน้อยเกินไปที่จะต้านทานการโจมตีที่ซับซ้อน มีเครื่องจักรออกมาซึ่งถอดรหัสรหัส 56 บิต (ลดขนาดตัวประกอบ 256) ด้วยปุ่ม 90 * 10 ^ 9 ต่อวินาทีใน 60 ชั่วโมง ใช้ 128 บิตหรือสองความยาว!
Thorsten S.

คำตอบ:


232

มาตรฐานของออราเคิล JDK ใช้ 7 การดำเนินการสิ่งที่เรียกว่าเครื่องกำเนิดไฟฟ้าเชิงเส้น congruential java.util.Randomการผลิตค่าสุ่ม

นำมาจากjava.util.Randomซอร์สโค้ด (JDK 7u2) จากความคิดเห็นเกี่ยวกับวิธีการprotected int next(int bits)ซึ่งเป็นสิ่งที่สร้างค่าสุ่ม:

นี่คือเครื่องกำเนิดเลขสุ่มเชิงเส้นสมภาคภูมิตามที่กำหนดโดย DH Lehmer และอธิบายโดย Donald E. Knuth ใน ศิลปะการเขียนโปรแกรมคอมพิวเตอร์เล่มที่ 3: อัลกอริธึม Seminumericalส่วน 3.2.1

ความสามารถในการทำนายของเครื่องกำเนิดไฟฟ้าเชิงเส้นตรง

Hugo Krawczyk เขียนบทความที่ดีเกี่ยวกับวิธีการทำนาย LCG เหล่านี้ ("วิธีการทำนายกำเนิดที่สอดคล้องกัน") หากคุณโชคดีและสนใจคุณอาจพบเวอร์ชันที่ดาวน์โหลดได้ฟรีบนเว็บ และมีงานวิจัยมากมายที่แสดงให้เห็นอย่างชัดเจนว่าคุณไม่ควรใช้ LCG เพื่อจุดประสงค์ด้านความปลอดภัย นอกจากนี้ยังหมายความว่าตัวเลขแบบสุ่มของคุณมีสิทธิที่คาดเดาได้ในขณะนี้สิ่งที่คุณไม่ต้องการรหัสเซสชันและชอบ

วิธีการทำลายเครื่องมือสร้างความสอดคล้องเชิงเส้น

การสันนิษฐานว่าผู้โจมตีจะต้องรอให้ LCG ทำซ้ำหลังจากครบวงจรผิด ถึงแม้จะมีวัฏจักรที่เหมาะสม (โมดูลัส m ในความสัมพันธ์การกลับเป็นซ้ำ) มันง่ายมากที่จะทำนายค่าในอนาคตในเวลาน้อยกว่าวงจรเต็ม ท้ายที่สุดมันเป็นเพียงสมการแบบแยกส่วนที่จำเป็นต้องแก้ไขซึ่งจะกลายเป็นเรื่องง่ายทันทีที่คุณสังเกตเห็นค่าเอาต์พุตที่เพียงพอของ LCG

ความปลอดภัยไม่ได้ปรับปรุงด้วยเมล็ดพันธุ์ที่ "ดีกว่า" มันไม่สำคัญว่าคุณเพาะเมล็ดด้วยค่าสุ่มที่สร้างขึ้นโดยSecureRandomหรือแม้แต่สร้างมูลค่าด้วยการกลิ้งดายหลายต่อหลายครั้ง

ผู้โจมตีเพียงแค่คำนวณเมล็ดจากค่าเอาต์พุตที่สังเกตได้ นี้จะใช้เวลาอย่างมีนัยสำคัญน้อยเวลากว่า 2 ^ 48 java.util.Randomในกรณีของ ผู้ที่ไม่เชื่ออาจลองการทดลองนี้ซึ่งแสดงให้เห็นว่าคุณสามารถทำนายRandomผลลัพธ์ในอนาคตที่สังเกตได้เพียงสองค่า (!) ในเวลาประมาณ 2 ^ 16 ใช้เวลาไม่กี่วินาทีในคอมพิวเตอร์สมัยใหม่ในการคาดการณ์ผลลัพธ์ของตัวเลขสุ่มของคุณในขณะนี้

ข้อสรุป

แทนที่รหัสปัจจุบันของคุณ ใช้SecureRandomเฉพาะ อย่างน้อยคุณก็จะรับประกันได้ว่าผลลัพธ์จะยากต่อการคาดเดา หากคุณต้องการคุณสมบัติของ PRNG ที่มีการเข้ารหัสที่ปลอดภัย (ในกรณีของคุณนั่นคือสิ่งที่คุณต้องการ) จากนั้นคุณต้องไปกับSecureRandomมันด้วย การฉลาดเกี่ยวกับการเปลี่ยนวิธีที่ควรจะใช้จะส่งผลให้สิ่งที่ปลอดภัยน้อยกว่า ...


4
มีประโยชน์มากคุณอาจอธิบายได้ว่า SecureRandom ทำงานอย่างไร (เหมือนคุณอธิบายวิธีการทำงานแบบสุ่ม) ..
gresdiplitude

4
นั่นเอาชนะจุดประสงค์ของ secureRandom
Azulflame

ฉันรู้แล้วเรียนรู้บทเรียนนั้นอย่างหนัก แต่แหล่งที่มาของ Cypher และยากต่อการค้นหาทำงานได้ดี รอยสามารถเรียนรู้บางสิ่งบางอย่างเกี่ยวกับเรื่องนั้น (เขาเข้ารหัสรหัสผ่านของผู้ใช้ในไฟล์. lastlogin เข้ารหัสด้วยการเข้ารหัสพื้นฐานโดยใช้ "รหัสผ่านไฟล์" เป็นกุญแจ)
Azulflame

1
คำถามจริงที่นี่: หาก java สามารถสร้าง prng ที่ปลอดภัยยิ่งขึ้นด้วย API ที่คล้ายกันทำไมพวกเขาไม่เพียงแค่แทนที่อันที่เสียหาย?
Joel Coehoorn

11
@JoelCoehoorn มันไม่ได้Randomหัก - มันควรจะใช้ในสถานการณ์ต่าง ๆ แน่นอนคุณสามารถใช้ SecureRandom ได้ตลอดเวลา แต่โดยทั่วไปแล้วจะเห็นได้ชัดช้ากว่าที่บริสุทธิ์SecureRandom Randomและมีหลายกรณีที่คุณสนใจเฉพาะคุณสมบัติทางสถิติที่ดีและประสิทธิภาพที่ยอดเยี่ยม แต่คุณไม่สนใจเรื่องความปลอดภัย: การจำลอง Monte-Carlo เป็นตัวอย่างที่ดี ฉันแสดงความคิดเห็นเกี่ยวกับสิ่งนั้นในคำตอบที่คล้ายกันบางทีคุณอาจพบว่ามันมีประโยชน์
นูน

72

การสุ่มมีเพียง 48 บิตที่ SecureRandom สามารถมีได้สูงสุด 128 บิต โอกาสในการทำซ้ำใน securerandom นั้นน้อยมาก

สุ่มใช้system clockเป็นเมล็ด / หรือเพื่อสร้างเมล็ด ดังนั้นพวกเขาสามารถทำซ้ำได้อย่างง่ายดายหากผู้โจมตีรู้เวลาที่สร้างเมล็ด แต่SecureRandomใช้เวลาRandom Dataจากคุณos(พวกเขาสามารถเป็นช่วงเวลาระหว่างการกดแป้น ฯลฯ - ระบบปฏิบัติการส่วนใหญ่รวบรวมข้อมูลเหล่านี้เก็บไว้ในไฟล์ - /dev/random and /dev/urandom in case of linux/solaris) และใช้มันเป็นเมล็ด
ดังนั้นหากขนาดโทเค็นขนาดเล็กก็โอเค (ในกรณีของ Random) คุณสามารถใช้รหัสของคุณต่อโดยไม่มีการเปลี่ยนแปลงใด ๆ เนื่องจากคุณใช้ SecureRandom เพื่อสร้างเมล็ด แต่ถ้าคุณต้องการโทเค็นขนาดใหญ่ (ซึ่งไม่สามารถทำได้brute force attacks) ไปกับ SecureRandom -
ในกรณีที่2^48จำเป็นต้องมีการสุ่มลองด้วยซีพียูขั้นสูงในปัจจุบันมันเป็นไปได้ที่จะทำลายมันในเวลาจริง แต่สำหรับ2^128ความพยายามในการสุ่มตัวอย่างจะต้องใช้ซึ่งจะใช้เวลาปีและปีที่จะทำลายแม้จะมีเครื่องที่ทันสมัยในปัจจุบัน

ดูลิงค์นี้สำหรับรายละเอียดเพิ่มเติม
แก้ไข
หลังจากอ่านลิงก์ที่ให้บริการโดย @emboss เป็นที่ชัดเจนว่าเมล็ดอย่างไรก็ตามการสุ่มอาจไม่ควรใช้กับ java.util.Random มันง่ายมากในการคำนวณเมล็ดโดยการสังเกตผลลัพธ์

ไปสำหรับ SecureRandom - ใช้Native PRNG (ตามที่ระบุในลิงก์ด้านบน) เพราะใช้ค่าสุ่มจาก/dev/randomไฟล์สำหรับการโทรแต่ละครั้งnextBytes(). วิธีนี้ผู้โจมตีการสังเกตการส่งออกจะไม่สามารถที่จะทำให้การออกอะไรถ้าเขาคือการควบคุมเนื้อหาของ/dev/randomไฟล์ (ซึ่งไม่น่ามาก) sha1 PRNGขั้นตอนวิธีการคำนวณเมล็ดเพียงครั้งเดียวและถ้า VM ของคุณใช้เวลาหลายเดือนโดยใช้แบบเดียวกัน เมล็ดมันอาจจะถูกโจมตีโดยผู้โจมตีที่กำลังเฝ้าสังเกตการส่งออกอย่างอดทน หมายเหตุ - หากคุณกำลังเรียกได้เร็วกว่าระบบปฏิบัติการของคุณสามารถที่จะเขียนไบต์สุ่ม (เอนโทรปี) ลงคุณอาจที่ดินเป็นปัญหาเมื่อใช้ พื้นเมือง PRNG ในกรณีดังกล่าวให้ใช้อินสแตนซ์ SHA1 PRNG ของ SecureRandom และทุก ๆ สองสามนาที (หรือบางช่วงเวลา) หว่านอินสแตนซ์นี้ด้วยค่าจาก


nextBytes()/dev/randomnextBytes()ของอินสแตนซ์ของ NATIVE PRNG ของ SecureRandom การรันทั้งสองอย่างขนานกันจะช่วยให้มั่นใจได้ว่าคุณกำลังทำการเพาะเมล็ดอย่างสม่ำเสมอด้วยค่าสุ่มที่แท้จริงในขณะที่ยังไม่หมดเอนโทรปีที่ได้จากระบบปฏิบัติการ


มันต้องการน้อยกว่า 2 ^ 48 ในการทำนาย a Random, OP ไม่ควรใช้Randomเลย
นูน

@emboss: ฉันกำลังพูดถึง bruteforce
Ashwin

1
ระวังด้วย Linux: มันสามารถเข้าถึงเอนโทรปีได้อย่างหมดแรง (ใน VM มากกว่ากับฮาร์ดแวร์)! ดู/proc/sys/kernel/random/entropy_availและตรวจสอบกับการทิ้งเธรดบางส่วนที่ไม่มีการรอนานเกินไปเมื่ออ่านบน/dev/random
Yves Martin

2
โปรดสังเกตว่า Oracle JRE (อย่างน้อย 1.7) ทำงานร่วมกับ / dev / urandom โดยค่าเริ่มต้นและไม่ / dev / สุ่มดังนั้นคำต่อท้ายคำตอบของคุณจะไม่ถูกต้องอีกต่อไป เพื่อตรวจสอบตรวจสอบ $ JAVA_HOME / lib / security / java.security สำหรับคุณสมบัติ securerandom.source
Boaz

1
ไฟล์ java.security ของเรามี securerandom.source = file: / dev / urandom แทนไฟล์: /// dev / urandom (สองสแลชหลังโคลอนสำหรับโพรโทคอลไฟล์จากนั้นทับอีกหนึ่งรูทสำหรับรูทของระบบไฟล์) ทำให้ถอยกลับ ถึง / dev / random ซึ่งก่อให้เกิดปัญหากับการหมดแรงเอนโทรปีของพูล ไม่สามารถแก้ไขได้ดังนั้นต้องตั้งค่าคุณสมบัติระบบ java.security.egd ให้เป็นค่าที่ถูกต้องเมื่อเริ่มต้นแอป
maxpolk

11

หากคุณรันสองครั้งjava.util.Random.nextLong()ด้วยเมล็ดเดียวกันมันจะสร้างหมายเลขเดียวกัน เพื่อเหตุผลด้านความปลอดภัยที่คุณต้องการใช้java.security.SecureRandomเพราะมันคาดเดาได้น้อยกว่ามาก

2 การเรียนการสอนมีความคล้ายคลึงกันผมคิดว่าคุณก็ต้องมีการเปลี่ยนแปลงRandomไปSecureRandomด้วยเครื่องมือ refactoring และส่วนใหญ่ของรหัสที่มีอยู่ของคุณจะทำงาน


11
หากคุณใช้สองอินสแตนซ์ของ PRNG ใด ๆ และเริ่มต้นด้วยค่าเดียวกันคุณจะได้รับหมายเลขสุ่มที่เหมือนกันเสมอแม้การใช้ SecureRandom จะไม่เปลี่ยนแปลง PRNG ทั้งหมดกำหนดขึ้นได้และคาดการณ์ได้ดังนั้นหากคุณรู้ว่าเมล็ด
Robert

1
มีการใช้งาน SecureRandom ที่แตกต่างกันบางอย่างเป็น PRNGs บางตัวไม่ ในทางตรงกันข้าม java.util.Random จะเป็น PRNG เสมอ (ตามที่กำหนดใน Javadoc)
ปีเตอร์ibrtibraný

3

หากการเปลี่ยนรหัสที่มีอยู่เป็นงานที่เหมาะสมฉันขอแนะนำให้คุณใช้คลาส SecureRandom ตามที่แนะนำใน Javadoc

แม้ว่าคุณจะพบว่าการใช้คลาสแบบสุ่มใช้คลาส SecureRandom ภายใน คุณไม่ควรรับมันเพราะ:

  1. การใช้ VM อื่น ๆ ก็ทำสิ่งเดียวกัน
  2. การใช้งานคลาส Random ในรุ่นอนาคตของ JDK ยังคงใช้คลาส SecureRandom

ดังนั้นจึงเป็นทางเลือกที่ดีกว่าในการทำตามคำแนะนำเอกสารและไปที่ SecureRandom โดยตรง


ผมไม่เชื่อว่าคำถามเดิมระบุว่าjava.util.Randomการดำเนินการใช้SecureRandomภายในก็กล่าวว่ารหัสของพวกเขาใช้เมล็ดSecureRandom Randomถึงกระนั้นฉันเห็นด้วยกับคำตอบทั้งสองเพื่อให้ห่างไกล; เป็นการดีที่สุดที่จะใช้SecureRandomเพื่อหลีกเลี่ยงโซลูชันที่กำหนดอย่างชัดเจน
Palpatim

2

การใช้การอ้างอิงในปัจจุบันของการjava.util.Random.nextLong()โทรสองครั้งไปยังเมธอดnext(int)ซึ่งเปิดเผย 32 บิตของเมล็ดปัจจุบันโดยตรง

protected int next(int bits) {
    long nextseed;
    // calculate next seed: ...
    // and store it in the private "seed" field.
    return (int)(nextseed >>> (48 - bits));
}

public long nextLong() {
    // it's okay that the bottom word remains signed.
    return ((long)(next(32)) << 32) + next(32);
}

32 บิตด้านบนของผลลัพธ์nextLong()เป็นบิตของเมล็ดในเวลา เนื่องจากความกว้างของเมล็ดเป็น 48 บิต (พูดว่า javadoc) มันพอเพียงที่จะวนซ้ำในส่วนที่เหลืออีก 16 บิต (นั่นเป็นเพียง 65.536 พยายาม) เพื่อกำหนดเมล็ดที่สร้าง 32 บิตที่สอง

เมื่อทราบว่าเมล็ดพันธุ์มีการคำนวณโทเค็นต่อไปนี้ทั้งหมดได้อย่างง่ายดาย

การใช้เอาต์พุตของnextLong()โดยตรงส่วนหนึ่งเป็นความลับของ PNG ในระดับที่ความลับทั้งหมดสามารถคำนวณได้ด้วย efford ที่น้อยมาก อันตราย!

* จำเป็นต้องใช้ความพยายามถ้า 32 บิตที่สองเป็นค่าลบ แต่สามารถค้นหาได้


แก้ไข. ดูวิธีถอดรหัส java.util.random ได้อย่างรวดเร็วที่jazzy.id.au/default/2010/09/20/… !
ingyhere

2

เมล็ดไม่มีความหมาย เครื่องกำเนิดไฟฟ้าแบบสุ่มที่ดีนั้นแตกต่างกันในชุดแรกที่เลือกไว้ ตัวสร้างแบบสุ่มทุกตัวเริ่มต้นจากตัวเลขแล้ววนซ้ำผ่าน 'เสียงเรียกเข้า' ซึ่งหมายความว่าคุณมาจากหมายเลขหนึ่งไปยังหมายเลขถัดไปพร้อมกับมูลค่าภายในเก่า แต่หลังจากนั้นไม่นานคุณก็มาถึงจุดเริ่มต้นอีกครั้งและเริ่มต้นใหม่อีกครั้ง ดังนั้นคุณจึงวิ่ง (ค่าส่งคืนจากตัวสร้างแบบสุ่มไม่ใช่ค่าภายใน)

หากคุณใช้หมายเลขเฉพาะสำหรับการสร้างเสียงเรียกเข้าหมายเลขทั้งหมดในแหวนนั้นจะถูกเลือกก่อนที่คุณจะครบรอบเต็มผ่านตัวเลขที่เป็นไปได้ทั้งหมด หากคุณเลือกตัวเลขที่ไม่สำคัญจะไม่ได้เลือกหมายเลขทั้งหมดและคุณจะได้รอบที่สั้นลง

ตัวเลขสำคัญที่สูงกว่าหมายถึงรอบที่ยาวกว่าก่อนที่คุณจะกลับไปที่องค์ประกอบแรกอีกครั้ง ดังนั้นตัวสร้างแบบสุ่มที่ปลอดภัยมีวงจรที่ยาวกว่าก่อนที่จะถึงจุดเริ่มต้นอีกครั้งนั่นคือสาเหตุที่ปลอดภัยกว่า คุณไม่สามารถทำนายการสร้างตัวเลขได้อย่างง่ายดายเหมือนกับรอบที่สั้นลง

ด้วยคำอื่น ๆ : คุณต้องแทนที่ทั้งหมด


0

ฉันจะพยายามใช้คำพื้นฐานเพื่อให้คุณสามารถเข้าใจความแตกต่างระหว่าง Random และ SecureRandom และความสำคัญของ SecureRandom Class ได้อย่างง่ายดาย

เคยสงสัยไหมว่า OTP (รหัสผ่านครั้งเดียว) ถูกสร้างขึ้นมาได้อย่างไร? ในการสร้าง OTP เราก็ใช้คลาส Random และ SecureRandom ตอนนี้เพื่อให้ OTP ของคุณแข็งแรง SecureRandom จะดีกว่าเพราะใช้เวลา 2 ^ 128 ลองทำการแตก OTP ซึ่งแทบจะเป็นไปไม่ได้ด้วยเครื่องปัจจุบัน แต่ถ้าใช้คลาสแบบสุ่มแล้ว OTP ของคุณจะถูกถอดรหัสโดยคนที่สามารถทำอันตรายข้อมูลของคุณได้ เพียงแค่ 2 ^ 48 ลองแตก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.