ฉันจะโต้แย้งข้อบกพร่องที่ยิ่งใหญ่ที่สุดstd::random_device
คืออนุญาตให้ใช้ทางเลือกที่กำหนดได้หากไม่มี CSPRNG นี่เป็นเหตุผลที่ดีที่จะไม่ใช้ PRNG std::random_device
เนื่องจากไบต์ที่สร้างขึ้นอาจถูกกำหนดได้ น่าเสียดายที่ไม่มี API เพื่อตรวจสอบเมื่อเกิดเหตุการณ์นี้ขึ้นหรือขอความล้มเหลวแทนการสุ่มตัวเลขคุณภาพต่ำ
นั่นคือไม่มีวิธีแก้ปัญหาแบบพกพาได้อย่างสมบูรณ์อย่างไรก็ตามมีแนวทางที่เหมาะสมและน้อยที่สุด คุณสามารถใช้กระดาษห่อเล็ก ๆ รอบ ๆ CSPRNG (กำหนดไว้sysrandom
ด้านล่าง) เพื่อเริ่มต้น PRNG
Windows
คุณสามารถพึ่งพาCryptGenRandom
CSPRNG ตัวอย่างเช่นคุณอาจใช้รหัสต่อไปนี้:
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
ของ