วิธีการเพาะเมล็ด mt19937 PRNG อย่างกระชับพอเพียงและละเอียด


113

ดูเหมือนว่าฉันจะเห็นคำตอบมากมายที่มีคนแนะนำให้ใช้<random>เพื่อสร้างตัวเลขสุ่มโดยปกติจะมีโค้ดดังนี้:

std::random_device rd;  
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);

โดยปกติสิ่งนี้จะแทนที่ "สิ่งที่น่าสะอิดสะเอียน" บางประเภทเช่น:

srand(time(NULL));
rand()%6;

เราอาจวิพากษ์วิจารณ์วิธีการเดิมโดยการโต้เถียงว่าtime(NULL)ให้เอนโทรปีต่ำtime(NULL)สามารถคาดเดาได้และผลลัพธ์สุดท้ายก็ไม่เหมือนกัน

แต่ทั้งหมดนี้เป็นความจริงในรูปแบบใหม่: มีเพียงแผ่นไม้อัดที่เงางามกว่า

  • rd()ส่งกลับไฟล์unsigned int. สิ่งนี้มีอย่างน้อย 16 บิตและอาจเป็น 32 บิตนั่นไม่เพียงพอที่จะเริ่มต้นสถานะ 19937 บิตของ MT

  • การใช้std::mt19937 gen(rd());gen()(การเพาะด้วย 32 บิตและดูที่ผลลัพธ์แรก) ไม่ได้ให้การกระจายเอาต์พุตที่ดี 7 และ 13 ไม่สามารถเป็นเอาต์พุตแรกได้ สองเมล็ดผลิต 0. สิบสองเมล็ดผลิต 1226181350. ( Link )

  • std::random_deviceอาจเป็นได้และบางครั้งก็ใช้เป็น PRNG แบบธรรมดาที่มีเมล็ดพันธุ์คงที่ ดังนั้นจึงอาจสร้างลำดับเดียวกันในทุกครั้ง ( เชื่อมโยง ) time(NULL)นี่คือเลวร้ายยิ่งกว่า

ยิ่งไปกว่านั้นการคัดลอกและวางข้อมูลโค้ดก่อนหน้านั้นทำได้ง่ายมากแม้ว่าจะมีปัญหาก็ตาม วิธีแก้ปัญหาบางอย่างจำเป็นต้องมีห้องสมุดขนาดใหญ่ ซึ่งอาจไม่เหมาะกับทุกคน

ด้วยเหตุนี้คำถามของฉันคือหนึ่งจะกระชับ, พกพา, และละเอียดอ่อน mt19937 PRNG ใน C ++ ได้อย่างไร?

เมื่อพิจารณาจากประเด็นข้างต้นคำตอบที่ดี:

  • ต้องเพาะเมล็ด mt19937 / mt19937_64 ให้เต็ม
  • ไม่สามารถพึ่งพาstd::random_deviceหรือtime(NULL)เป็นแหล่งที่มาของเอนโทรปีได้
  • ไม่ควรพึ่งพา Boost หรือ Libaries อื่น ๆ
  • ควรใส่ในจำนวนบรรทัดเล็ก ๆ เพื่อให้สำเนาวางในคำตอบดูดี

ความคิด

  • ความคิดปัจจุบันของฉันคือผลลัพธ์ที่ได้จากstd::random_deviceสามารถบด (อาจผ่าน XOR) ด้วยtime(NULL)ค่าที่ได้จากการสุ่มพื้นที่แอดเดรสและค่าคงที่ฮาร์ดโค้ด (ซึ่งสามารถตั้งค่าได้ระหว่างการกระจาย) เพื่อให้ได้ภาพที่เอนโทรปีอย่างเต็มที่

  • std::random_device::entropy() ไม่ได้เป็นตัวบ่งชี้ที่ดีถึงสิ่งที่std::random_deviceอาจทำหรือไม่ได้


24
@ ฟาเบียน: พกพาอะไรประมาณนั้น? นี่คือคำถาม C ++ ไม่ใช่คำถามสำหรับ Linux
Lightness Races ใน Orbit

6
ความคิดส่วนตัวของผมก็คือว่าบางทีค่าอาจจะมาจากstd::random_device, time(NULL)และฟังก์ชั่นอยู่แล้ว XORed ร่วมกันในการผลิตชนิดของแหล่งที่มาของเอนโทรปีที่ดีที่สุดความพยายาม
Richard

5
คงจะดีถ้ามีฟังก์ชั่นเช่น does_random_device_actually_work () อย่างน้อยก็สามารถลดระดับลงอย่างสง่างามหรือสร้างคำเตือนหรือข้อผิดพลาดสำหรับผู้ใช้

4
วิธีแก้ปัญหาที่เหมาะสมไม่สั้นวิธีแก้ปัญหาสั้น ๆ จะไม่เหมาะสม แนวทางของฉันที่ฉันใช้ในไลบรารี seed11ของฉันนั้นโดยพื้นฐานแล้วคือการนำไปใช้std::random_deviceอย่างถูกต้องบนแพลตฟอร์มที่คุณวางแผนจะรันโปรแกรมของคุณและมีฟังก์ชันตัวช่วยที่สร้างเครื่องกำเนิดเมล็ดพันธุ์ ( seed11::make_seeded<std::mt19937>())
milleniumbug

5
นอกเหนือ: สัญลักษณ์แสดงหัวข้อย่อยที่สองของคุณไม่ได้เพิ่มอะไรใหม่ ไม่น่าแปลกใจที่คุณพบคุณค่าบางอย่างที่ปรากฏขึ้น 12 ครั้ง คุณควรคาดหวังว่าจะมีค่ามากกว่าสามค่าที่ปรากฏเป็น 12 เท่าโดยสมมติว่าคุณมี 2 ^ 32 ตัวอย่างสุ่มแบบสม่ำเสมอ

คำตอบ:


58

ฉันจะโต้แย้งข้อบกพร่องที่ยิ่งใหญ่ที่สุดstd::random_deviceคืออนุญาตให้ใช้ทางเลือกที่กำหนดได้หากไม่มี CSPRNG นี่เป็นเหตุผลที่ดีที่จะไม่ใช้ PRNG std::random_deviceเนื่องจากไบต์ที่สร้างขึ้นอาจถูกกำหนดได้ น่าเสียดายที่ไม่มี API เพื่อตรวจสอบเมื่อเกิดเหตุการณ์นี้ขึ้นหรือขอความล้มเหลวแทนการสุ่มตัวเลขคุณภาพต่ำ

นั่นคือไม่มีวิธีแก้ปัญหาแบบพกพาได้อย่างสมบูรณ์อย่างไรก็ตามมีแนวทางที่เหมาะสมและน้อยที่สุด คุณสามารถใช้กระดาษห่อเล็ก ๆ รอบ ๆ CSPRNG (กำหนดไว้sysrandomด้านล่าง) เพื่อเริ่มต้น PRNG

Windows


คุณสามารถพึ่งพาCryptGenRandomCSPRNG ตัวอย่างเช่นคุณอาจใช้รหัสต่อไปนี้:

bool acquire_context(HCRYPTPROV *ctx)
{
    if (!CryptAcquireContext(ctx, nullptr, nullptr, PROV_RSA_FULL, 0)) {
        return CryptAcquireContext(ctx, nullptr, nullptr, PROV_RSA_FULL, CRYPT_NEWKEYSET);
    }
    return true;
}


size_t sysrandom(void* dst, size_t dstlen)
{
    HCRYPTPROV ctx;
    if (!acquire_context(&ctx)) {
        throw std::runtime_error("Unable to initialize Win32 crypt library.");
    }

    BYTE* buffer = reinterpret_cast<BYTE*>(dst);
    if(!CryptGenRandom(ctx, dstlen, buffer)) {
        throw std::runtime_error("Unable to generate random bytes.");
    }

    if (!CryptReleaseContext(ctx, 0)) {
        throw std::runtime_error("Unable to release Win32 crypt library.");
    }

    return dstlen;
}

Unix-Like


ในระบบที่คล้าย Unix หลายระบบคุณควรใช้/ dev / urandomเมื่อเป็นไปได้ (แม้ว่าจะไม่รับประกันว่าจะมีอยู่ในระบบที่รองรับ POSIX)

size_t sysrandom(void* dst, size_t dstlen)
{
    char* buffer = reinterpret_cast<char*>(dst);
    std::ifstream stream("/dev/urandom", std::ios_base::binary | std::ios_base::in);
    stream.read(buffer, dstlen);

    return dstlen;
}

อื่น ๆ


ถ้าไม่มี CSPRNG std::random_deviceสามารถใช้ได้คุณอาจเลือกที่จะพึ่งพา อย่างไรก็ตามฉันจะหลีกเลี่ยงสิ่งนี้หากเป็นไปได้เนื่องจากคอมไพเลอร์ต่างๆ (โดยเฉพาะอย่างยิ่ง MinGW) ใช้มันเป็นPRNG (ในความเป็นจริงสร้างลำดับเดียวกันทุกครั้งเพื่อเตือนมนุษย์ว่ามันไม่ได้สุ่มอย่างถูกต้อง)

การเพาะเมล็ด


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

std::uint_least32_t seed;    
sysrandom(&seed, sizeof(seed));
std::mt19937 gen(seed);

เปรียบเทียบกับ Boost


เราสามารถมองเห็นแนวเพื่อเพิ่ม :: random_device (ก CSPRNG จริง) หลังจากที่ดูอย่างรวดเร็วที่รหัสที่มา Boost ใช้MS_DEF_PROVบน Windows ซึ่งเป็นประเภทผู้ให้บริการสำหรับPROV_RSA_FULL. CRYPT_VERIFYCONTEXTสิ่งเดียวที่ขาดหายไปจะได้รับการตรวจสอบบริบทการเข้ารหัสลับซึ่งสามารถทำได้ด้วย เมื่อวันที่ * /dev/urandomระวังเพิ่มการใช้งาน IE โซลูชันนี้เป็นแบบพกพาผ่านการทดสอบอย่างดีและใช้งานง่าย

ความเชี่ยวชาญของ Linux


หากคุณยินดีที่จะสละความรวบรัดเพื่อความปลอดภัยgetrandomเป็นตัวเลือกที่ยอดเยี่ยมบน Linux 3.17 ขึ้นไปและ Solaris ล่าสุด getrandomทำงานเหมือนกัน/dev/urandomยกเว้นว่าจะบล็อกหากเคอร์เนลยังไม่เริ่มต้น CSPRNG หลังจากบูต ตรวจพบตัวอย่างต่อไปนี้ถ้าลินุกซ์สามารถใช้ได้และหากไม่อยู่กลับไปยังgetrandom/dev/urandom

#if defined(__linux__) || defined(linux) || defined(__linux)
#   // Check the kernel version. `getrandom` is only Linux 3.17 and above.
#   include <linux/version.h>
#   if LINUX_VERSION_CODE >= KERNEL_VERSION(3,17,0)
#       define HAVE_GETRANDOM
#   endif
#endif

// also requires glibc 2.25 for the libc wrapper
#if defined(HAVE_GETRANDOM)
#   include <sys/syscall.h>
#   include <linux/random.h>

size_t sysrandom(void* dst, size_t dstlen)
{
    int bytes = syscall(SYS_getrandom, dst, dstlen, 0);
    if (bytes != dstlen) {
        throw std::runtime_error("Unable to read N bytes from CSPRNG.");
    }

    return dstlen;
}

#elif defined(_WIN32)

// Windows sysrandom here.

#else

// POSIX sysrandom here.

#endif

OpenBSD


มีอยู่คนหนึ่งข้อแม้สุดท้ายคือ: OpenBSD /dev/urandomทันสมัยไม่ได้ คุณควรใช้getentropyแทน

#if defined(__OpenBSD__)
#   define HAVE_GETENTROPY
#endif

#if defined(HAVE_GETENTROPY)
#   include <unistd.h>

size_t sysrandom(void* dst, size_t dstlen)
{
    int bytes = getentropy(dst, dstlen);
    if (bytes != dstlen) {
        throw std::runtime_error("Unable to read N bytes from CSPRNG.");
    }

    return dstlen;
}

#endif

ความคิดอื่น ๆ


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

สามารถทำได้อย่างง่ายดายโดยเปลี่ยนsysrandomเป็น:

size_t sysrandom(void* dst, size_t dstlen)
{
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd == -1) {
        throw std::runtime_error("Unable to open /dev/urandom.");
    }
    if (read(fd, dst, dstlen) != dstlen) {
        close(fd);
        throw std::runtime_error("Unable to read N bytes from CSPRNG.");
    }

    close(fd);
    return dstlen;
}

ขอบคุณ


ขอขอบคุณ Ben Voigt เป็นพิเศษสำหรับการชี้ให้เห็นการFILEใช้การอ่านบัฟเฟอร์ดังนั้นจึงไม่ควรใช้

ผมยังต้องการที่จะขอบคุณปีเตอร์คอร์ดสำหรับการกล่าวขวัญgetrandomและการขาด OpenBSD /dev/urandomของ


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

2
OP ที่นี่: คงจะดีถ้าคำตอบนี้แสดงให้เห็นถึงการเพาะเมล็ดที่ดีขึ้นเล็กน้อย มากที่สุดเท่าที่จะเป็นไปได้ฉันหวังว่าจะได้คำตอบที่สร้างโค้ดที่สามารถคัดลอกได้ซึ่งทำงานได้ดีกว่าตัวอย่างง่ายๆที่ฉันโพสต์ไว้ในคำถามของฉันโดยไม่ต้องใช้การตีความทางเทคนิคหรือความคิดในส่วนของ coder
Richard

4
ฉันคิดว่า/dev/randomน่าจะเป็นทางเลือกที่ดีกว่าสำหรับการเพาะ RNG แต่เห็นได้ชัดว่า/dev/urandomยังถือว่าปลอดภัยในการคำนวณแม้ว่า/dev/randomจะถูกบล็อกเนื่องจากเอนโทรปีที่มีอยู่ต่ำก็ตามดังนั้นจึงurandomเป็นทางเลือกที่แนะนำสำหรับทุกสิ่งยกเว้นอาจเป็นแผ่นรองครั้งเดียว ดูเพิ่มเติมunix.stackexchange.com/questions/324209/... ระวังเมล็ดพันธุ์ที่คาดเดาได้ตั้งแต่urandomช่วงแรก ๆ หลังจากการบูตเครื่อง
Peter Cordes

2
การgetrandom(2)เรียกระบบของ Linux ก็เหมือนกับการเปิดและการอ่าน/dev/urandomยกเว้นจะบล็อกหากยังไม่ได้เริ่มต้นแหล่งการสุ่มของเคอร์เนล ฉันคิดว่าสิ่งนี้ช่วยให้คุณประหยัดจากปัญหาการสุ่มคุณภาพต่ำในช่วงเริ่มต้นโดยไม่ปิดกั้นในกรณีอื่น ๆ เช่น/dev/randomทำ
Peter Cordes

1
@PeterCordes แน่นอนและเป็นตัวเลือกที่ยอดเยี่ยมเมื่อพร้อมใช้งาน อย่างไรก็ตามมันไม่ทำงานบน BSD หรือ * Nixes อื่น ๆ ซึ่งเป็นสิ่งที่ใช้/dev/urandomงานได้โดยทั่วไป การอภิปรายรายชื่อผู้รับจดหมาย Python เกี่ยวกับเรื่องนี้เป็นสิ่งที่ฉันสมัครเป็นสมาชิกโดยทั่วไป: bugs.python.org/issue27266
Alexander Huszagh

22

ในแง่หนึ่งสิ่งนี้ไม่สามารถทำได้แบบพกพา นั่นคือเราสามารถสร้างแพลตฟอร์มที่ถูกกำหนดอย่างสมบูรณ์ที่ใช้ C ++ ได้ (กล่าวคือเครื่องจำลองที่กำหนดขั้นตอนนาฬิกาของเครื่องตามกำหนดและด้วย I / O "กำหนด") ซึ่งไม่มีแหล่งที่มาของการสุ่มเพื่อเริ่มต้น PRNG


1
@kbelder: 1. ใครบอกว่าผู้ใช้เป็นคน? 2. ไม่ใช่ทุกโปรแกรมที่มีการโต้ตอบกับผู้ใช้และคุณไม่สามารถคาดเดาได้อย่างแน่นอนว่ามีผู้ใช้อยู่รอบ ๆ ...
einpoklum

8
ฉันขอขอบคุณคำตอบนี้ แต่ก็รู้สึกราวกับว่าโปรแกรมควรพยายามอย่างเต็มที่ตามสมควร
Richard

3
@Richard เห็นด้วย แต่ปัญหาคือนักเขียนมาตรฐาน C ++ ต้อง (หรืออย่างน้อยก็พยายามที่จะยากที่สุด) เพื่อรองรับสถานการณ์ที่แปลกประหลาดเหล่านี้ นั่นเป็นเหตุผลที่คุณได้รับคำจำกัดความมาตรฐานที่ปรารถนาซึ่งคุณอาจได้รับผลลัพธ์ที่ดี แต่คอมไพเลอร์ยังคงเป็นไปตามมาตรฐานแม้ว่าจะให้สิ่งที่ไร้ค่าในการทำงานกลับคืนมาก็ตาม - ดังนั้นข้อ จำกัด ของคุณ ("สั้นและไม่สามารถพึ่งพาไลบรารีอื่นได้") จึงตัดการตอบสนองใด ๆ ออกไปเนื่องจากคุณต้องการปลอกพิเศษแบบทีละแพลตฟอร์ม / คอมไพเลอร์โดยคอมไพเลอร์ (เช่นสิ่งที่ Boost ทำได้ดี)
RM

2
@ ริชาร์ดสิ่งที่อธิบายคือคุณได้รับสิ่งที่คุณได้รับในมาตรฐานเนื่องจากไม่มีวิธีพกพาที่จะทำได้ดีกว่า หากคุณต้องการที่จะทำได้ดีกว่านี้ (ซึ่งเป็นเป้าหมายที่สูงส่ง) คุณจะต้องยอมรับสิ่งที่น่าสะอิดสะเอียนจำนวนมากขึ้นหรือน้อยลง :)
hobbs

1
@ ริชาร์ด: บางครั้งคุณต้องยอมรับว่าเป็นไปได้ที่จะใช้งาน C ++ ที่เป็นไปตามมาตรฐานซึ่งไม่มีประโยชน์ เนื่องจากการใช้งานที่ผู้คนใช้สำหรับสิ่งที่สำคัญได้รับการออกแบบมาเพื่อให้เป็นประโยชน์บางครั้งคุณจึงต้องอยู่กับข้อโต้แย้งเช่น "การนำไปใช้อย่างมีเหตุผลจะทำสิ่งที่สมเหตุสมผล" ฉันหวังว่าstd::random_deviceจะอยู่ในหมวดหมู่นั้น แต่เห็นได้ชัดว่าไม่ใช่ถ้าการใช้งานจริงบางอย่างใช้ PRNG คงที่! นั่นไปไกลกว่าข้อโต้แย้งของ einpoklum
Peter Cordes

14

คุณสามารถใช้ a std::seed_seqและเติมให้เป็นอย่างน้อยขนาดสถานะที่ต้องการสำหรับเครื่องกำเนิดไฟฟ้าโดยใช้วิธีการรับเอนโทรปีของ Alexander Huszagh:

size_t sysrandom(void* dst, size_t dstlen); //from Alexander Huszagh answer above

void foo(){

    std::array<std::mt19937::UIntType, std::mt19937::state_size> state;
    sysrandom(state.begin(), state.length*sizeof(std::mt19937::UIntType));
    std::seed_seq s(state.begin(), state.end());

    std::mt19937 g;
    g.seed(s);
}

หากมีวิธีที่เหมาะสมในการเติมหรือสร้างSeedSequenceจากUniformRandomBitGeneratorในไลบรารีมาตรฐานที่ใช้std::random_deviceสำหรับการเพาะเมล็ดอย่างถูกต้องจะง่ายกว่ามาก


1
seed_seq มีปัญหาแม้ว่าpcg-random.org/posts/developing-a-seed_seq-alternative.html
etarion

ไม่มีสิ่งใดในมาตรฐาน C ++ หรือมีการรับประกันว่าตัวสร้างตัวเลขสุ่มจะใช้อาร์เรย์ทั้งหมดเมื่อคุณเริ่มต้นจาก seed_seq วิธีนี้จะนำไปสู่ความล้มเหลวหากคุณใช้ rng สำหรับการจำลองทางวิทยาศาสตร์และเห็นได้ชัดว่าสำหรับการเข้ารหัส เกี่ยวกับกรณีการใช้งานเดียวสำหรับสิ่งนี้คือการสุ่มวิดีโอเกม แต่จะมีการใช้งานมากเกินไป
Kostas

5

การใช้งานที่ฉันกำลังดำเนินการใช้ประโยชน์จากstate_sizeคุณสมบัติของmt19937PRNG เพื่อตัดสินใจว่าจะให้เมล็ดพันธุ์จำนวนเท่าใดในการเริ่มต้น:

using Generator = std::mt19937;

inline
auto const& random_data()
{
    thread_local static std::array<typename Generator::result_type, Generator::state_size> data;
    thread_local static std::random_device rd;

    std::generate(std::begin(data), std::end(data), std::ref(rd));

    return data;
}

inline
Generator& random_generator()
{
    auto const& data = random_data();

    thread_local static std::seed_seq seeds(std::begin(data), std::end(data));
    thread_local static Generator gen{seeds};

    return gen;
}

template<typename Number>
Number random_number(Number from, Number to)
{
    using Distribution = typename std::conditional
    <
        std::is_integral<Number>::value,
        std::uniform_int_distribution<Number>,
        std::uniform_real_distribution<Number>
    >::type;

    thread_local static Distribution dist;

    return dist(random_generator(), typename Distribution::param_type{from, to});
}

ฉันคิดว่ามีช่องว่างสำหรับการปรับปรุงเนื่องจากstd::random_device::result_typeอาจstd::mt19937::result_typeมีขนาดและช่วงที่แตกต่างกันดังนั้นจึงควรนำมาพิจารณาด้วย

หมายเหตุเกี่ยวกับมาตรฐาน :: random_device

ตามC++11(/14/17)มาตรฐาน:

26.5.6 Class random_device [ rand.device ]

2หากข้อ จำกัด ในการนำไปใช้งานป้องกันการสร้างตัวเลขสุ่มที่ไม่ได้กำหนดขึ้นการใช้งานอาจใช้กลไกตัวเลขสุ่ม

ซึ่งหมายความว่าการนำไปใช้อาจสร้างค่าที่กำหนดได้ก็ต่อเมื่อมีการป้องกันไม่ให้สร้างค่าที่ไม่ได้กำหนดโดยข้อ จำกัด บางประการ

MinGWคอมไพเลอร์ในWindowsที่มีชื่อเสียงไม่ได้ให้ที่ไม่ใช่การกำหนดค่าจากมันstd::random_deviceแม้จะมีพวกเขาเป็นอย่างง่ายดายจากระบบปฏิบัติการ ดังนั้นฉันจึงคิดว่านี่เป็นข้อบกพร่องและไม่น่าจะเกิดขึ้นบ่อยในการใช้งานและแพลตฟอร์ม


1
สิ่งนี้อาจเติมเต็มสถานะ MT แต่ยังคงอาศัยเพียงอย่างเดียวstd::random_deviceดังนั้นจึงเสี่ยงต่อปัญหาที่เกิดจากมัน
Richard

1
ฉันคิดว่าฉันระบุไว้ชัดเจนเพียงพอในคำถาม ยินดีที่จะชี้แจง / อภิปรายแม้ว่า
Richard

2
@ ริชาร์ดมีระบบใดบ้างที่ไม่ได้ใช้งานจริงอย่างสมเหตุสมผลstd::random_device? ฉันรู้ว่ามาตรฐานอนุญาตให้PRNGถอยกลับได้ แต่ฉันรู้สึกว่านั่นเป็นเพียงการปกปิดตัวเองเนื่องจากเป็นการยากที่จะเรียกร้องให้อุปกรณ์ทุกชิ้นที่ใช้C++มีแหล่งที่มาแบบสุ่มที่ไม่ได้กำหนด แล้วถ้าพวกนั้นทำไม่ได้แล้วคุณจะทำยังไงต่อล่ะ?
Galik

5
@AlexanderHuszagh ฉันไม่แน่ใจ ความตั้งใจของผมคือการทำให้ "การแก้ปัญหาแบบพกพา" ฉันขึ้นอยู่กับอุปกรณ์ที่std::random_deviceเพราะถ้าอุปกรณ์สนับสนุนเครื่องกำเนิดไฟฟ้าที่ไม่ได้กำหนดแล้วดังนั้นไม่ควร ฉันเชื่อว่านั่นคือจิตวิญญาณของมาตรฐาน ดังนั้นฉันได้ค้นหาและพบMinGWว่ามีการหักในส่วนนี้เท่านั้น ดูเหมือนจะไม่มีใครรายงานปัญหานี้กับสิ่งอื่นที่ฉันพบ ดังนั้นในห้องสมุดของฉันฉันจึงทำเครื่องหมายMinGWว่าไม่รองรับ หากมีปัญหาที่กว้างขึ้นฉันจะคิดใหม่ ฉันไม่เห็นหลักฐานของสิ่งนั้นในตอนนี้
Galik

5
ฉันผิดหวังจริงๆที่ MinGW ทำลายstd::random_deviceทุกคนด้วยการทำให้มันพร้อมใช้งานในรูปแบบที่ไม่ส่งมอบความสามารถในการสุ่มของแพลตฟอร์ม การใช้งานที่มีคุณภาพต่ำเอาชนะจุดประสงค์ของ API ที่มีอยู่ IMO จะดีกว่าถ้าพวกเขาไม่ได้นำไปใช้เลยจนกว่าจะใช้งานได้ (หรือดีกว่าถ้า API ให้วิธีการร้องขอความล้มเหลวหากไม่มีการสุ่มคุณภาพสูงดังนั้น MinGW จึงสามารถหลีกเลี่ยงความเสี่ยงด้านความปลอดภัยในขณะที่ยังให้เมล็ดพันธุ์ที่แตกต่างกันสำหรับเกมหรืออะไรก็ตาม)
Peter Cordes

2

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

คุณลักษณะที่ดีอย่างหนึ่งของแนวทางนี้คือเป็นการสรุปถึงการเริ่มต้นจากชุดเมล็ดพืชอื่น ๆ ที่ไม่ได้สุ่มจริงๆ ตัวอย่างเช่นหากคุณต้องการให้แต่ละเธรดมี RNG ของตัวเอง (สำหรับเธรดความปลอดภัย) คุณสามารถเริ่มต้นตาม ID เธรดที่แฮชได้

ต่อไปนี้เป็นSSCCE ที่กลั่นจากcodebase ของฉัน (เพื่อความเรียบง่ายโครงสร้างรองรับ OO บางส่วนถูกกำจัดออกไป):

#include <cstdint> //`uint32_t`
#include <functional> //`std::hash`
#include <random> //`std::mt19937`
#include <iostream> //`std::cout`

static std::mt19937 rng;

static void seed(uint32_t seed) {
    rng.seed(static_cast<std::mt19937::result_type>(seed));
}
static void seed() {
    uint32_t t = static_cast<uint32_t>( time(nullptr) );
    std::hash<uint32_t> hasher; size_t hashed=hasher(t);
    seed( static_cast<uint32_t>(hashed) );
}

int main(int /*argc*/, char* /*argv*/[]) {
    seed();
    std::uniform_int_distribution<> dis(0, 5);
    std::cout << dis(rng);
}

1
ฉันเห็นด้วยกับประเด็นของคุณที่ว่าการเพาะเมล็ดตามเวลานั้นน่าจะดีพอในทางปฏิบัติหากคุณไม่ต้องการให้มันปลอดภัย แต่ฉันไม่เห็นด้วยกับคำตอบที่เหลือของคุณ การเพาะเมล็ดโดยใช้เวลาไม่ดีไปกว่าการเพาะเมล็ดด้วยเวลาเอง
DW

@DW สังเกตุก็คือมากดีกว่า เหตุผลก็คือว่ากัญชาเป็นต่อเนื่องและครอบคลุมช่วงกว้างมากของค่า (ลองนี้ด้วยตัวเอง: เมล็ด1และ2และสังเกตว่าลำดับของลอยที่สร้างโดยพวกเขาจะใช้เวลาในขณะที่เปลี่ยนไปจริงๆ)
imallett

ฉันไม่เห็นว่าทำไมถึงสำคัญ เราวิ่งไปทีละเมล็ดเท่านั้น ช่องว่างของค่าที่เป็นไปได้สำหรับเมล็ดพันธุ์ (เอนโทรปีของเมล็ดพันธุ์) ก็เหมือนกัน - การแฮชไม่ได้เพิ่มเอนโทรปี บางทีคุณอาจแก้ไขคำถามเพื่ออธิบายว่าเหตุใดการแฮชจึงดีกว่า
DW

0

นี่คือคำถามของฉันเอง:

#include <random>
#include <chrono>
#include <cstdint>
#include <algorithm>
#include <functional>
#include <iostream>

uint32_t LilEntropy(){
  //Gather many potential forms of entropy and XOR them
  const  uint32_t my_seed = 1273498732; //Change during distribution
  static uint32_t i = 0;        
  static std::random_device rd; 
  const auto hrclock = std::chrono::high_resolution_clock::now().time_since_epoch().count();
  const auto sclock  = std::chrono::system_clock::now().time_since_epoch().count();
  auto *heap         = malloc(1);
  const auto mash = my_seed + rd() + hrclock + sclock + (i++) +
    reinterpret_cast<intptr_t>(heap)    + reinterpret_cast<intptr_t>(&hrclock) +
    reinterpret_cast<intptr_t>(&i)      + reinterpret_cast<intptr_t>(&malloc)  +
    reinterpret_cast<intptr_t>(&LilEntropy);
  free(heap);
  return mash;
}

//Fully seed the mt19937 engine using as much entropy as we can get our
//hands on
void SeedGenerator(std::mt19937 &mt){
  std::uint_least32_t seed_data[std::mt19937::state_size];
  std::generate_n(seed_data, std::mt19937::state_size, std::ref(LilEntropy));
  std::seed_seq q(std::begin(seed_data), std::end(seed_data));
  mt.seed(q);
}

int main(){
  std::mt19937 mt;
  SeedGenerator(mt);

  for(int i=0;i<100;i++)
    std::cout<<mt()<<std::endl;
}

แนวคิดในที่นี้คือการใช้ XOR เพื่อรวมแหล่งที่มาของเอนโทรปีที่เป็นไปได้จำนวนมาก (เวลาที่รวดเร็วเวลาช้าstd::random-deviceตำแหน่งตัวแปรคงตำแหน่งฮีปตำแหน่งฟังก์ชันตำแหน่งไลบรารีค่าเฉพาะโปรแกรม) เพื่อพยายามอย่างเต็มที่ในการเริ่มต้น mt19937 ตราบเท่าที่แหล่งที่มานั้น "ดี" อย่างน้อยหนึ่งครั้งผลลัพธ์ก็จะเป็น "ดี"

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


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

1
ฉันเดาว่าน่า&i ^ &myseedจะมีเอนโทรปีน้อยกว่าอย่างใดอย่างหนึ่งอย่างมากเนื่องจากทั้งสองเป็นวัตถุที่มีระยะเวลาการจัดเก็บแบบคงที่ในหน่วยการแปลเดียวกันดังนั้นจึงมีแนวโน้มที่จะค่อนข้างใกล้กัน และดูเหมือนคุณจะไม่ได้ใช้ค่าพิเศษจากการเริ่มต้นของmyseed?
aschepler

7
การแปลงพอยน์เตอร์ที่ได้รับการจัดการเป็น ints เป็นพฤติกรรมที่ไม่ได้กำหนด ทำในขณะที่ยังมีอยู่ ^เป็นเครื่องผสมกัญชาที่น่ากลัว หากทั้งสองค่ามีเอนโทรปีมาก แต่เมื่อเทียบกันน้อยค่านั้นจะลบออก +มักจะดีกว่า (เนื่องจาก x + x เผาเอนโทรปีได้เพียง 1 บิตใน x ในขณะที่ x ^ x เบิร์นทั้งหมด) ฟังก์ชั่นไม่ปลอดภัยสำหรับผู้ใช้ฉันสงสัยว่า ( rd())
Yakk - Adam Nevraumont

2
โอ้และ+ฉันหมายถึงไม่ได้ลงนาม ( +เมื่อลงชื่อคือ UB-bait) แม้ว่าเคสเหล่านี้จะค่อนข้างไร้สาระ แต่คุณก็บอกว่าพกพาได้ พิจารณาหาที่อยู่ของฟังก์ชันเป็นค่าอินทิกรัลด้วยถ้าเป็นไปได้ (ไม่แน่ใจว่าเป็นหรือไม่)
Yakk - Adam Nevraumont

1
@meneldal: แม้ในพีซีที่มีความแข็งแรงเต็มรูปแบบแม้ว่าการจัดสรรอาจได้รับตำแหน่งทางกายภาพที่แตกต่างกัน (ขึ้นอยู่กับสถานะของเครื่องภายนอกกระบวนการ) ตัวชี้จะถูกสรุปโดยพื้นที่ที่อยู่เสมือนของกระบวนการและมีแนวโน้มที่จะทำซ้ำได้สูงโดยเฉพาะอย่างยิ่งคือ ASLR ไม่มีผลบังคับใช้
Ben Voigt

0
  • ใช้ getentropy () เพื่อสร้างตัวสร้างหมายเลขหลอก (PRNG)
  • ใช้ getrandom () หากคุณต้องการสุ่มค่า (แทนการพูด/dev/urandomหรือ/dev/random)

สิ่งเหล่านี้มีอยู่ในระบบที่เหมือน UNIX สมัยใหม่เช่น Linux, Solaris และ OpenBSD


-2

แพลตฟอร์มที่ระบุอาจมีแหล่งที่มาของเอนโทรปีเช่น/dev/random. นาโนวินาทีนับตั้งแต่ยุคที่มีstd::chrono::high_resolution_clock::now()อาจเป็นเมล็ดพันธุ์ที่ดีที่สุดในห้องสมุดมาตรฐาน

ก่อนหน้านี้ฉันเคยใช้บางอย่างเช่น(uint64_t)( time(NULL)*CLOCKS_PER_SEC + clock() )รับเอนโทรปีเพิ่มเติมสำหรับแอปพลิเคชันที่ไม่สำคัญด้านความปลอดภัย


2
คุณควรใช้/dev/urandomโดยเฉพาะอย่างยิ่งในกรณีเช่นนี้ /dev/randomบล็อกและมักไม่มีเหตุผลที่ดีในการทำเช่นนั้น ([ใส่คำอธิบายแบบยาวเกี่ยวกับจำนวนระบบปฏิบัติการต่างๆที่ประมาณการสุ่มของไบต์ที่สร้างโดย / dev / random])
Alexander Huszagh

2
@AlexanderHuszagh True แม้ว่าฉันจะต้องเขียนโค้ดในระบบที่/dev/urandomไม่มีอยู่จริงและทางเลือกอื่นในการบล็อกคือปัจจัยกำหนด กล่องอาจมี/dev/hwrngหรือ/dev/hw_randomเช่นกันซึ่งควรจะดีกว่านี้
Davislor

โอเคฉันพูดว่า“ เช่น/dev/random” และดูเหมือนว่าจะจุดชนวนให้เกิดสงครามศักดิ์สิทธิ์/dev/randomกับ/dev/urandomลินุกซ์ที่ฉันไม่ได้ตั้งใจเมื่อฉันยกตัวอย่างนั้น ..
Davislor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.