จะใช้ /dev/random หรือ urandom ใน C ได้อย่างไร?

ฉันต้องการใช้ /dev/random หรือ /dev/urandom ใน C จะต้องทำอย่างไร? ฉันไม่รู้ว่าจะจัดการพวกมันใน C ได้อย่างไร หากใครรู้ช่วยบอกฉันหน่อยว่าต้องทำอย่างไร ขอบคุณ


person stojance    schedule 03.04.2010    source แหล่งที่มา
comment
ลองอ่านบทความที่ให้ความรู้นี้เกี่ยวกับคำเตือนทั่วไปบางประการในการใช้เส้นทางนี้ไปสู่ ​​(pseudo-)randomness: insanecoding.blogspot.fi/2014/05/   -  person appas    schedule 17.06.2015


คำตอบ (5)


โดยทั่วไป ควรหลีกเลี่ยงการเปิดไฟล์เพื่อรับข้อมูลแบบสุ่ม เนื่องจากมีจุดที่เกิดความล้มเหลวในขั้นตอนนี้กี่จุด

ในลีนุกซ์รุ่นใหม่ล่าสุด สามารถใช้การเรียกระบบ getrandom เพื่อ รับตัวเลขสุ่มที่ปลอดภัยด้วยการเข้ารหัสลับ และไม่สามารถล้มเหลวได้ หาก GRND_RANDOM ไม่ได้ ระบุเป็นแฟล็ก และจำนวนการอ่านสูงสุด 256 ไบต์

ณ เดือนตุลาคม 2017 ขณะนี้ OpenBSD, Darwin และ Linux (ที่มี -lbsd) ต่างก็มีการใช้งาน arc4random ที่ปลอดภัยด้วยการเข้ารหัสลับและไม่สามารถล้มเหลวได้ นั่นทำให้มันเป็นตัวเลือกที่น่าสนใจมาก:

char myRandomData[50];
arc4random_buf(myRandomData, sizeof myRandomData); // done!

มิฉะนั้น คุณสามารถใช้อุปกรณ์สุ่มราวกับว่ามันเป็นไฟล์ได้ คุณอ่านจากพวกเขาแล้วคุณจะได้รับข้อมูลแบบสุ่ม ฉันใช้ open/read ที่นี่ แต่ fopen/fread ก็ใช้ได้เช่นกัน

int randomData = open("/dev/urandom", O_RDONLY);
if (randomData < 0)
{
    // something went wrong
}
else
{
    char myRandomData[50];
    ssize_t result = read(randomData, myRandomData, sizeof myRandomData);
    if (result < 0)
    {
        // something went wrong
    }
}

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

/dev/random นั้นพิถีพิถันมากกว่า บนแพลตฟอร์มส่วนใหญ่ ระบบสามารถส่งคืนไบต์น้อยกว่าที่คุณขอ และสามารถบล็อกได้หากมีไบต์ไม่เพียงพอ สิ่งนี้ทำให้เรื่องราวการจัดการข้อผิดพลาดซับซ้อนมากขึ้น:

int randomData = open("/dev/random", O_RDONLY);
if (randomData < 0)
{
    // something went wrong
}
else
{
    char myRandomData[50];
    size_t randomDataLen = 0;
    while (randomDataLen < sizeof myRandomData)
    {
        ssize_t result = read(randomData, myRandomData + randomDataLen, (sizeof myRandomData) - randomDataLen);
        if (result < 0)
        {
            // something went wrong
        }
        randomDataLen += result;
    }
    close(randomData);
}
person zneak    schedule 03.04.2010
comment
โอเค คำตอบที่ดี แล้วถ้าต้องการตั้งค่าให้อ่านหลายตัวเลขล่ะ ? ฉันจะทำ while(something) { read(..) } หรือฉันควรเปิด/ปิดมันทุกครั้งที่เริ่มวนซ้ำ? - person stojance; 03.04.2010
comment
สวัสดี คุณช่วยบอกเคล็ดลับหน่อยได้ไหมว่าฉันจะอ่านไบต์ทั้งหมดอย่างต่อเนื่องจาก \dev\random ได้อย่างไร ถ้ามันขวางอยู่ ฉันจะสัมผัสมันได้อย่างไร แต่อีกครั้งเมื่อมีไบต์มากขึ้น ฉันจะอ่านมัน - person karim; 02.11.2010
comment
@karim มันไม่ได้บล็อกบน Mac OS (ที่ฉันใช้) และฉันไม่แน่ใจเกี่ยวกับวิธีการจัดการกับไฟล์บล็อกสำหรับสถานการณ์เหล่านี้ คุณควรถามคำถามใหม่เกี่ยวกับเรื่องนี้ - person zneak; 02.11.2010
comment
@karim: โปรดอย่าอ่านไบต์ทั้งหมดจาก /dev/random แค่อย่า โปรแกรมของคุณอาจไม่ใช่ผู้ใช้ เพียงคนเดียว ในระบบที่ต้องการไบต์แบบสุ่ม - person Zan Lynx; 26.02.2011
comment
ฉันต้องการเพียง 256 ไบต์ แล้วฉันก็หยุด แต่บางครั้งฉันก็ไม่ได้รับ 256 ไบต์ ฉันต้องรอประมาณ 1/2 วินาที - person karim; 26.02.2011
comment
รหัสในคำตอบนี้ผิดในขณะนี้ คุณต้องตรวจสอบค่าที่ส่งคืนของ read เพื่อดูว่ามีการอ่านจริงกี่ไบต์ และวนซ้ำจนกว่าคุณจะได้รับเพียงพอ ตามที่เป็นอยู่ รหัสนี้อาจทำให้ myRandomInteger เต็มไปด้วยหน่วยความจำเก่า แทนที่จะเป็นข้อมูลแบบสุ่ม - person morrog; 25.10.2013
comment
@morrog ฉันเชื่อว่าการแก้ไขของฉันจะแก้ไขปัญหาที่คุณหยิบยกขึ้นมา อย่าลืมว่าคุณสามารถแก้ไขคำตอบของใครก็ได้หากคุณรู้สึกว่าไม่เพียงพอ - person zneak; 25.10.2013
comment
@zneak ขออภัยฉันลืมไปว่าสามารถแก้ไขโพสต์ได้ที่นี่ ขอบคุณสำหรับการให้ฉันรู้ว่า. การแก้ไขครั้งล่าสุดของคุณเพิ่มการตรวจสอบการอ่านเท่านั้น แทนที่จะเป็นการวนซ้ำ ฉันแก้ไขให้ใช้การวนซ้ำ ดังนั้นโค้ดจะถูกบล็อกอย่างเหมาะสมแล้ว - person morrog; 26.10.2013
comment
@morrog ผู้ตรวจสอบ Overeager ปฏิเสธ ดังนั้นฉันจึงทำการเปลี่ยนแปลงด้วยตนเอง ขออภัยที่คุณไม่ได้รับเครดิตสำหรับมัน - person zneak; 27.10.2013
comment
จริงๆ แล้ว นักเข้ารหัสลับส่วนใหญ่แนะนำให้ใช้ /dev/urandom หากมีการเพาะอย่างเพียงพอ หนึ่งครั้ง มันจะส่งออกตัวเลขสุ่มหลอกจำนวนไม่จำกัดซึ่งเหมาะสำหรับใช้ในสกุลเงินดิจิทัล สิ่งเดียวที่ต้องกังวลก็คือการเพาะเมล็ดครั้งแรกอาจไม่เพียงพอ ปัญหาที่สุ่มน้อยกว่าเกิดขึ้นคือกรณีพิเศษบางกรณีที่เกิดขึ้นไม่บ่อยนัก เช่น ในช่วงต้นของกระบวนการบูตบนอุปกรณ์ฝังตัวหรือเครื่องเสมือนโคลน - person CodesInChaos; 27.10.2013
comment
@CodesInChaos ฉัน อ้างอิงถึง Linux manpage: หากคุณไม่แน่ใจว่าคุณ ควรใช้ /dev/random หรือ /dev/urandom จากนั้นคุณอาจต้องการใช้อันหลัง ตามกฎทั่วไป /dev/urandom ควรใช้กับทุกสิ่ง ยกเว้นคีย์ GPG/SSL/SSH ที่มีอายุการใช้งานยาวนาน - person zneak; 27.10.2013
comment
@zneak เน้นที่ อายุยืนยาว เพราะสำหรับคนที่หวาดระแวงเป็นพิเศษก็ไม่เจ็บ สำหรับการเข้ารหัสลับปกติ การใช้ /dev/urandom ก็ใช้ได้ - person CodesInChaos; 27.10.2013
comment
อย่างไรก็ตาม คุณต้องแน่ใจว่า /dev/random (หรือ /dev/urandom) เป็นอุปกรณ์ตัวเลขสุ่มจริงๆ และไม่ใช่ไฟล์กระจัดกระจายหรือลิงก์ไปยัง /dev/zero (ซึ่งอาจเกิดขึ้นหากเครื่องของคุณถูกแฮ็ก) มีบทความดีๆ อยู่ที่ insanecoding .blogspot.co.uk/2014/05/ ที่กล่าวถึงข้อผิดพลาดของการใช้ /dev/{u}สุ่มสี่สุ่มห้า - person Chris J; 23.07.2014
comment
สิ่งนี้จำเป็นต้องตรวจสอบค่าส่งคืนจากการเรียก open(2) หรือไม่? - person KyleWpppd; 02.01.2015
comment
@KyleWpppd คำตอบนี้ในตอนแรกจะตรวจสอบว่าไม่มีเงื่อนไขข้อผิดพลาดเลยเพื่อความเรียบง่าย แต่มีคนอื่นตัดสินใจว่ามันสำคัญพอที่จะรวมไว้ประมาณ read ฉันว่าโดยทั่วไปคุณควรตรวจสอบเงื่อนไขข้อผิดพลาดเสมอ นอกจากนี้ หากแอปพลิเคชันของคุณมีความสำคัญต่อความปลอดภัย อาจเป็นความคิดที่ดีที่จะตรวจสอบว่าไฟล์นั้นเป็นอุปกรณ์แบบอักขระ - person zneak; 02.01.2015
comment
คำตอบนี้นั่งอยู่ที่นี่มา 5 ปีแล้ว แต่ฉันเชื่อว่ารหัสผิด คุณไม่เคยใช้ file descriptor randomData นอกเหนือจากการปิดมันด้วยซ้ำ คุณแน่ใจว่ามันไม่ควรอ่าน read(randomData ...? แสดงความสำคัญของการตั้งชื่อ - person BoppreH; 01.08.2015
comment
@BoppreH คุณคงดีใจที่ได้รู้ว่าปัญหาเฉพาะนี้มีมาประมาณสองปีแล้ว (มีคนอื่นแนะนำการเปลี่ยนแปลงที่ฉันลงเอยด้วยการรวมตัวเองเข้าด้วยกันโดยไม่ระมัดระวังเพียงพอ) เมื่อพูดถึงการแนะนำการแก้ไข คุณสามารถแก้ไขปัญหาอื่นๆ ที่คุณพบได้ - person zneak; 01.08.2015
comment
ย่อหน้าสุดท้ายของคำตอบนี้เป็นการตั้งสมมติฐานที่ไม่ถูกต้อง อธิบายไว้ที่นี่: 2uo.de/ myths-about-urandom/#structure ในความเป็นจริง ทั้ง /dev/random และ /dev/urandom อยู่หลัง CSPRNG เดียวกัน (คำแนะนำของคุณในการเลือก /dev/urandom มากกว่า /dev/random ยังคงอยู่) - person Sebastian; 23.03.2017

มีคำตอบที่ถูกต้องอื่นๆ ข้างต้น ฉันจำเป็นต้องใช้สตรีม FILE* นี่คือสิ่งที่ฉันทำ...

int byte_count = 64;
char data[64];
FILE *fp;
fp = fopen("/dev/urandom", "r");
fread(&data, 1, byte_count, fp);
fclose(fp);
person Dustin Kirkland    schedule 16.08.2012
comment
int สามารถอ่านได้โดยตรงโดยเพียงแค่ส่ง int pointer ไปยัง char pointer fread((char*)(&myInt),sizeof(myInt),1,fp) - person Azeem Bande-Ali; 25.05.2013
comment
@ AzeemBande-Ali: ทำไมคุณไม่ใช้ fread((int*)(&myInt),sizeof(myInt),1,fp) แทน? ฉันหมายถึงนักแสดงเพื่อ int* ? - person Larry; 30.05.2014
comment
ไม่ว่าในกรณีใดไม่ควรใช้การร่ายในโค้ด C fread() รับ void * ดังนั้นเพียงแค่ทำ fread(&myInt, ... ); - person nos; 03.01.2015
comment
ทำไมคุณถึงต้องการ byte_count? มันไม่ได้ใช้. - person CalculatorFeline; 10.08.2017
comment
@CalculatorFeline byte_count ที่นี่ค่อนข้างสับสน op อาจต้องการให้แต่ละดัชนีมีความยาวไบต์เท่ากันในตอนแรก แต่ก็ไม่สามารถทำได้ ... - person LinconFive; 19.03.2020

เพียงเปิดไฟล์เพื่ออ่านแล้วอ่านข้อมูล ใน C ++ 11 คุณอาจต้องการใช้ std::random_device ซึ่งให้การเข้าถึงข้ามแพลตฟอร์มไปยังอุปกรณ์ดังกล่าว

person Tronic    schedule 03.04.2010
comment
ปรากฏว่า std::random_device ไม่ผ่านมาตรฐานปี 2011 โดยปรากฏในฉบับร่าง N3797 - person Keith Thompson; 02.01.2015
comment
ดูเหมือนว่า std::random_device ได้ ทำให้เป็น C ++ 11 ในตอนท้าย - person legends2k; 26.06.2015
comment
ปัญหาคือ std::random_device อยู่ใน C++ และไม่ใช่ใน C และ OP ถามวิธีใช้ /dev/random หรือ /dev/urandom ไม่ใช่วิธีใช้ std::random_device แม้ว่าจะเป็นทางเลือกที่ดีที่จะใช้ std::random_device และมีประโยชน์ แต่ก็ไม่ได้เป็นเพียงสิ่งที่ OP ขอ - person Nfagie Yansaneh; 08.08.2017

Zneak ถูกต้อง 100% เป็นเรื่องปกติมากที่จะอ่านบัฟเฟอร์ของตัวเลขสุ่มที่มีขนาดใหญ่กว่าที่คุณต้องการเมื่อเริ่มต้นเล็กน้อย จากนั้นคุณสามารถเติมอาร์เรย์ในหน่วยความจำหรือเขียนลงในไฟล์ของคุณเองเพื่อนำกลับมาใช้ใหม่ในภายหลังได้

การใช้งานทั่วไปของสิ่งข้างต้น:

typedef struct prandom {
     struct prandom *prev;
     int64_t number;
     struct prandom *next;
} prandom_t;

สิ่งนี้จะกลายเป็นเหมือนเทปที่เลื่อนไปข้างหน้าซึ่งสามารถเติมด้ายอื่นได้อย่างน่าอัศจรรย์ตามต้องการ มี จำนวนมาก จาก บริการ ที่ให้การถ่ายโอนไฟล์ขนาดใหญ่ซึ่งไม่มีอะไรเลยนอกจากตัวเลขสุ่มที่สร้างขึ้นด้วยตัวสร้างที่แข็งแกร่งกว่ามาก เช่น:

  • การสลายตัวของสารกัมมันตรังสี
  • พฤติกรรมทางแสง (โฟตอนชนกระจกกึ่งโปร่งใส)
  • เสียงบรรยากาศ (ไม่แรงเท่าข้างต้น)
  • ฟาร์มลิงขี้เมาพิมพ์คีย์บอร์ดและหนูเคลื่อนไหว (ล้อเล่น)

อย่าใช้เอนโทรปี "บรรจุล่วงหน้า" สำหรับเมล็ดที่เข้ารหัส ในกรณีที่ไม่ได้ดำเนินการโดยไม่บอกกล่าว ชุดเหล่านั้นใช้ได้สำหรับการจำลอง ไม่ดีเลย สำหรับการสร้างคีย์และอื่นๆ

ไม่ต้องกังวลเรื่องคุณภาพ หากคุณต้องการตัวเลขจำนวนมากสำหรับบางอย่างเช่นการจำลองมอนติคาร์โล จะดีกว่ามากถ้ามีตัวเลขเหล่านั้นในลักษณะที่ไม่ทำให้ read() ถูกบล็อก

อย่างไรก็ตาม โปรดจำไว้ว่า การสุ่มของตัวเลขนั้นขึ้นอยู่กับปัจจัยที่กำหนดพอๆ กับความซับซ้อนในการสร้างตัวเลขนั้น /dev/random และ /dev/urandom สะดวก แต่ไม่แรงเท่ากับการใช้ HRNG (หรือการดาวน์โหลดดัมพ์ขนาดใหญ่จาก HRNG) นอกจากนี้ ควรสังเกตด้วยว่า /dev/random เติมผ่านเอนโทรปี ดังนั้นจึงสามารถบล็อกได้ค่อนข้างมาก ในขณะที่ขึ้นอยู่กับสถานการณ์

person Tim Post♦    schedule 04.04.2010
comment
การดาวน์โหลดไฟล์ขนาดใหญ่ที่ไม่มีอะไรเลยนอกจากตัวเลขสุ่มถือเป็นคำแนะนำที่แย่มากสำหรับวัตถุประสงค์ในการเข้ารหัส มีการขอให้บุคคลอื่นจัดหาเมล็ดพันธุ์ให้กับฟังก์ชันของคุณ และบริการเหล่านั้นดูเหมือนจะถ่ายโอนข้อมูลนั้นโดยไม่ได้เข้ารหัสผ่านทางอินเทอร์เน็ต กรุณาอย่าทำอย่างนั้น. - person dequis; 09.07.2014
comment
@dequis ฉันชี้แจงแล้ว ฉันไม่เห็นปัญหาในการใช้พวกมันเพื่อรันการจำลองขนาดใหญ่ ค่อนข้าง คิดว่าคงเป็นเรื่องปกติที่จะไม่ใช้สำหรับ keygen / ฯลฯ แต่มันก็คุ้มค่าที่จะเจาะจงประเด็นนั้นอย่างแปลกประหลาด คำถามนี้เป็นคำถามที่ไม่เชื่อเรื่องพระเจ้า ดังนั้นฉันไม่ได้คิดเจาะจงมากนัก แต่เป็นประเด็นที่ดี - person Tim Post♦; 28.07.2015

คำตอบของ zneak ครอบคลุมอย่างเรียบง่าย แต่ความจริงนั้นซับซ้อนกว่านั้น ตัวอย่างเช่น คุณต้องพิจารณาว่า /dev/{u}random เป็นอุปกรณ์ตัวเลขสุ่มจริงๆ หรือไม่ตั้งแต่แรก สถานการณ์ดังกล่าวอาจเกิดขึ้นได้หากเครื่องของคุณถูกโจมตีและอุปกรณ์แทนที่ด้วยลิงก์สัญลักษณ์ไปที่ /dev/zero หรือไฟล์กระจัดกระจาย หากสิ่งนี้เกิดขึ้น กระแสสุ่มก็สามารถคาดเดาได้อย่างสมบูรณ์แล้ว

วิธีที่ง่ายที่สุด (อย่างน้อยบน Linux และ FreeBSD) คือทำการเรียก ioctl บนอุปกรณ์ที่จะสำเร็จก็ต่อเมื่ออุปกรณ์นั้นเป็นเครื่องกำเนิดแบบสุ่ม:

int data;
int result = ioctl(fd, RNDGETENTCNT, &data); 
// Upon success data now contains amount of entropy available in bits

หากดำเนินการนี้ก่อนที่จะอ่านอุปกรณ์สุ่มครั้งแรก ก็ถือว่ามีเดิมพันที่ยุติธรรมว่าคุณมีอุปกรณ์สุ่ม ดังนั้นคำตอบของ @zneak จึงสามารถขยายเป็น:

int randomData = open("/dev/random", O_RDONLY);
int entropy;
int result = ioctl(randomData, RNDGETENTCNT, &entropy);

if (!result) {
   // Error - /dev/random isn't actually a random device
   return;
}

if (entropy < sizeof(int) * 8) {
    // Error - there's not enough bits of entropy in the random device to fill the buffer
    return;
}

int myRandomInteger;
size_t randomDataLen = 0;
while (randomDataLen < sizeof myRandomInteger)
{
    ssize_t result = read(randomData, ((char*)&myRandomInteger) + randomDataLen, (sizeof myRandomInteger) - randomDataLen);
    if (result < 0)
    {
        // error, unable to read /dev/random 
    }
    randomDataLen += result;
}
close(randomData);

บล็อก Insane Coding กล่าวถึงเรื่องนี้ และข้อผิดพลาดอื่นๆเมื่อไม่นานมานี้ ฉันขอแนะนำให้อ่านบทความทั้งหมด ฉันต้องให้เครดิตพวกเขาว่าโซลูชันนี้ถูกดึงมาจากไหน

แก้ไขเพื่อเพิ่ม (25-07-2557)...
บังเอิญฉันอ่านเจอเมื่อคืนนี้ว่าเป็นส่วนหนึ่งของ ความพยายามของ LibReSSL ดูเหมือน Linux จะได้รับ GetRandom() syscall. ในขณะที่เขียนยังไม่มีคำว่าจะพร้อมใช้งานในเคอร์เนลทั่วไปเมื่อใด อย่างไรก็ตาม นี่จะเป็นอินเทอร์เฟซที่ต้องการเพื่อรับข้อมูลสุ่มที่มีความปลอดภัยแบบเข้ารหัส เนื่องจากจะลบข้อผิดพลาดทั้งหมดที่เข้าถึงผ่านไฟล์ที่มีให้ ดูเพิ่มเติมที่ LibReSSL การใช้งานที่เป็นไปได้

person Chris J    schedule 23.07.2014
comment
ผู้โจมตีที่มีพลังเพียงพอที่จะแทนที่ /dev/random หรือ /dev/urandom ด้วยสิ่งอื่น โดยทั่วไปแล้วจะมีพลังเพียงพอที่จะโหลดโมดูลเคอร์เนลเพื่อทำลายทุกความพยายามที่คุณทำในการพิจารณาว่าเป็นอุปกรณ์สุ่มหรือไม่ - person zneak; 02.01.2015
comment
man page ระบุว่า getrandom() เปิดตัวในเคอร์เนล 3.17 ดังนั้นหุ้น Ubuntu 16.04 จึงไม่มี ณ วันที่ 17-01-2018 เรียกใช้ uname -a ในเทอร์มินัลเพื่อตรวจสอบเวอร์ชันเคอร์เนลของคุณ - person erapert; 17.01.2018