Je dirais que le plus grand défaut de avec std::random_device
est la qu'il est permis à un déterministe de secours si aucune CSPRNG est disponible. Cela seul est une bonne raison de ne pas semer un GÉNÉRATEUR à l'aide de std::random_device
, depuis les octets produit peut être déterministe. Il ne marche malheureusement pas fournir une API pour savoir quand cela se produit, ou à la demande de l'échec au lieu de la basse-qualité de nombres aléatoires.
Qui est, il n'est pas complètement portable solution: cependant, il est décent, approche minimale. Vous pouvez utiliser un petit wrapper autour d'une CSPRNG (défini comme l' sysrandom
ci-dessous) pour déclencher le PRNG.
Windows
Vous pouvez compter sur CryptGenRandom
, un CSPRNG. Par exemple, vous pouvez utiliser le code suivant:
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
Sur de nombreux systèmes Unix, vous devez utiliser /dev/urandom quand c'est possible (même si cela n'est pas garanti pour exister sur POSIX systèmes).
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;
}
D'autres
Si aucun CSPRNG est disponible, vous pouvez choisir de s'appuyer sur std::random_device
. Cependant, je voudrais éviter si possible, étant donné que divers compilateurs (notamment, MinGW) mettre en œuvre avec un PRNG (en fait, la production de la même séquence à chaque fois pour alerter les humains qu'il n'est pas correctement aléatoire).
Semis
Maintenant que nous avons nos pièces avec un minimum de frais généraux, nous pouvons générer de l'souhaité bits aléatoires de l'entropie à la graine de notre GÉNÉRATEUR. L'exemple utilise (évidemment insuffisant) 32-bits pour déclencher le PRNG, et vous pouvez augmenter cette valeur (qui dépend de votre CSPRNG).
std::uint_least32_t seed;
sysrandom(&seed, sizeof(seed));
std::mt19937 gen(seed);
Comparaison De Boost
Nous pouvons voir des parallèles avec boost::random_device (un vrai CSPRNG) après un rapide coup d'œil au code source. Boost utilise MS_DEF_PROV
sur Windows, qui est le type de fournisseur de PROV_RSA_FULL
. La seule chose qui manque serait de vérifier le chiffrement contexte, ce qui peut être fait avec CRYPT_VERIFYCONTEXT
. Sur *Nix, Boost utilise /dev/urandom
. C'est à dire, cette solution est portable, bien testé, et facile à utiliser.
Linux Spécialisation
Si vous êtes prêt à sacrifier la concision de la sécurité, getrandom
est un excellent choix sur Linux 3.17 et au-dessus, et sur ces derniers Solaris. getrandom
se comporte de manière identique à l' /dev/urandom
, à l'exception des blocs si le noyau n'a pas initialisé son CSPRNG encore après le démarrage. L'extrait de code suivant détecte si Linux getrandom
est disponible, et si on ne retombe à l' /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
Il y a une dernière mise en garde: moderne OpenBSD n'a pas /dev/urandom
. Vous devez utiliser getentropy à la place.
#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
D'Autres Pensées
Si vous avez besoin d'cryptographique sécurisé octets aléatoires, vous devrez probablement remplacer le fstream avec POSIX est sans tampon ouvrir/lire/fermer. C'est parce que les deux basic_filebuf
et FILE
contiennent une mémoire tampon interne, qui sera allouée par l'intermédiaire d'un allocateur standard (et donc pas effacé de la mémoire).
Cela pourrait facilement être fait en changeant sysrandom
à la:
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;
}
Merci
Un merci spécial à Ben Voigt pour souligner FILE
utilise tampon lit, et, par conséquent, ne doit pas être utilisé.
Je tiens également à remercier Peter Cordes pour mentionner getrandom
, et OpenBSD manque d' /dev/urandom
.