Twierdzę, że największą wadą std::random_device
jest to, że dozwolone jest deterministyczne rozwiązanie awaryjne, jeśli nie jest dostępny CSPRNG. Już samo to jest dobrym powodem, aby nie wysiewać PRNG przy użyciu std::random_device
, ponieważ produkowane bajty mogą być deterministyczne. Niestety nie zapewnia interfejsu API, aby dowiedzieć się, kiedy tak się stanie, lub zażądać błędu zamiast niskiej jakości liczb losowych.
Oznacza to, że nie ma całkowicie przenośnego rozwiązania: istnieje jednak przyzwoite, minimalne podejście. Możesz użyć minimalnego opakowania wokół CSPRNG (zdefiniowanego sysrandom
poniżej), aby zainicjować PRNG.
Windows
Możesz polegać na CryptGenRandom
CSPRNG. Na przykład możesz użyć następującego kodu:
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;
}
Podobne do systemu Unix
W wielu systemach uniksopodobnych powinieneś używać / dev / urandom, jeśli to możliwe (chociaż nie ma gwarancji, że będzie istnieć w systemach zgodnych z 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;
}
Inny
Jeśli nie ma dostępnego CSPRNG, możesz polegać std::random_device
. Jednak unikałbym tego, jeśli to możliwe, ponieważ różne kompilatory (w szczególności MinGW) implementują go jako PRNG (w rzeczywistości produkują tę samą sekwencję za każdym razem, aby ostrzegać ludzi, że nie jest ona właściwie losowa).
Wysiew
Teraz, gdy mamy już nasze elementy z minimalnym narzutem, możemy wygenerować pożądane bity losowej entropii, aby zasiać nasz PRNG. W przykładzie użyto (oczywiście niewystarczających) 32-bitowych do zapoczątkowania PRNG i powinieneś zwiększyć tę wartość (która jest zależna od twojego CSPRNG).
std::uint_least32_t seed;
sysrandom(&seed, sizeof(seed));
std::mt19937 gen(seed);
Porównanie do wzmocnienia
Widzimy podobieństwa do boost :: random_device (prawdziwy CSPRNG) po szybkim spojrzeniu na kod źródłowy . Boost używa MS_DEF_PROV
w systemie Windows, który jest typem dostawcy dla PROV_RSA_FULL
. Jedyne, czego brakowało, to zweryfikowanie kontekstu kryptograficznego, z którym można to zrobić CRYPT_VERIFYCONTEXT
. W * Nix Boost używa /dev/urandom
. IE, to rozwiązanie jest przenośne, dobrze przetestowane i łatwe w użyciu.
Specjalizacja w Linuksie
Jeśli chcesz poświęcić zwięzłość dla bezpieczeństwa, getrandom
jest to doskonały wybór na Linuksie 3.17 i nowszych oraz na najnowszym Solarisie. getrandom
zachowuje się identycznie /dev/urandom
, z wyjątkiem tego, że blokuje się, jeśli jądro nie zainicjowało swojego CSPRNG jeszcze po uruchomieniu. Poniższy fragment kodu wykrywa, czy Linux getrandom
jest dostępny, a jeśli nie, wraca do /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
Jest jeszcze jedno ostatnie zastrzeżenie: współczesne OpenBSD nie ma /dev/urandom
. Zamiast tego powinieneś użyć 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
inne przemyślenia
Jeśli potrzebujesz zabezpieczonych kryptograficznie losowych bajtów, prawdopodobnie powinieneś zamienić fstream na niebuforowane open / read / close POSIX. Dzieje się tak, ponieważ oba basic_filebuf
i FILE
zawierają wewnętrzny bufor, który zostanie przydzielony przez standardowy alokator (a zatem nie zostanie usunięty z pamięci).
Można to łatwo zrobić, zmieniając sysrandom
na:
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;
}
Dzięki
Specjalne podziękowania dla Bena Voigta za wskazanie FILE
zastosowań odczytów buforowanych i dlatego nie należy ich używać.
Chciałbym również podziękować Peterowi Cordesowi za wspomnienie getrandom
i brak OpenBSD /dev/urandom
.