Я б заперечував, що найбільшим недоліком 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);
Порівняння для підвищення
Ми можемо побачити паралелі для підвищення :: random_device (справжній CSPRNG) після швидкого огляду вихідного коду . Boost використовує MS_DEF_PROV
для Windows, для якого є провайдером PROV_RSA_FULL
. Єдине, чого бракує, - це перевірка криптографічного контексту, що можна зробити CRYPT_VERIFYCONTEXT
. Увімкнено * Nix, використовує Boost /dev/urandom
. IE, це рішення є портативним, добре перевіреним та простим у використанні.
Спеціалізація Linux
Якщо ви готові пожертвувати лаконічністю заради безпеки, getrandom
це відмінний вибір на Linux 3.17 і вище, а також на останніх Solaris. getrandom
поводиться ідентично /dev/urandom
, за винятком того, що блокує, якщо ядро ще не ініціалізувало CSPRNG після завантаження. Наступний фрагмент визначає, чи getrandom
доступний Linux , а якщо не, то повертається до /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
. Натомість слід використовувати гетентропію .
#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;
}
Дякую
Особлива подяка Бену Вогту за те, що він вказує на FILE
використання буферизованого читання, і тому його не слід використовувати.
Я також хотів би подякувати Пітеру Кордесу за згадування getrandom
та відсутність OpenBSD /dev/urandom
.