Il me semble voir de nombreuses réponses dans lesquelles quelqu'un suggère d'utiliser <random>
pour générer des nombres aléatoires, généralement avec un code comme celui-ci:
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 5);
dis(gen);
Habituellement, cela remplace une sorte d '«abomination impie» telle que:
srand(time(NULL));
rand()%6;
Nous pourrions critiquer l'ancienne méthode en affirmant qu'elle time(NULL)
fournit une faible entropie, qu'elle time(NULL)
est prévisible et que le résultat final n'est pas uniforme.
Mais tout cela est vrai de la nouvelle façon: il a juste un placage plus brillant.
rd()
renvoie un seulunsigned int
. Cela a au moins 16 bits et probablement 32. Cela ne suffit pas pour amorcer les bits d'état 19937 de MT.Utiliser
std::mt19937 gen(rd());gen()
(ensemencer avec 32 bits et regarder la première sortie) ne donne pas une bonne distribution de sortie. 7 et 13 ne peuvent jamais être la première sortie. Deux graines produisent 0. Douze graines produisent 1226181350. ( Lien )std::random_device
peut être, et est parfois, implémenté comme un simple PRNG avec une graine fixe. Il peut donc produire la même séquence à chaque exécution. ( Lien ) C'est encore pire quetime(NULL)
.
Pire encore, il est très facile de copier et coller les extraits de code précédents, malgré les problèmes qu'ils contiennent. Certaines solutions à cela nécessitent l'acquisition de grandes bibliothèques qui peuvent ne pas convenir à tout le monde.
À la lumière de cela, ma question est: Comment peut-on semer succinctement, de manière portable et complète le PRNG mt19937 en C ++?
Compte tenu des problèmes ci-dessus, une bonne réponse:
- Doit semer complètement le mt19937 / mt19937_64.
- Ne peut pas compter uniquement sur
std::random_device
outime(NULL)
comme source d'entropie. - Ne devrait pas compter sur Boost ou d'autres bibliothèques.
- Doit tenir dans un petit nombre de lignes de sorte que cela semble bien copié-collé dans une réponse.
Pensées
Ma pensée actuelle est que les sorties de
std::random_device
peuvent être mélangées (peut-être via XOR) avec destime(NULL)
valeurs dérivées de la randomisation de l'espace d'adressage et une constante codée en dur (qui pourrait être définie lors de la distribution) pour obtenir un meilleur effort d'entropie.std::random_device::entropy()
ne donne pas une bonne indication de ce quistd::random_device
pourrait ou ne pourrait pas faire.
std::random_device
,time(NULL)
et des adresses de fonction, puis XORed ensemble pour produire une sorte de source d'entropie au mieux.std::random_device
correctement sur les plates-formes sur lesquelles vous prévoyez d'exécuter votre programme et à fournir une fonction d'assistance qui crée un générateur prédéfini (seed11::make_seeded<std::mt19937>()
)Réponses:
Je dirais que le plus grand défaut
std::random_device
est le fait qu'il est autorisé à un repli déterministe si aucun CSPRNG n'est disponible. Ceci seul est une bonne raison de ne pas amorcer un PRNG en utilisantstd::random_device
, car les octets produits peuvent être déterministes. Il ne fournit malheureusement pas d'API pour savoir quand cela se produit, ou pour demander un échec au lieu de nombres aléatoires de mauvaise qualité.Autrement dit, il n'y a pas de solution complètement portable : cependant, il existe une approche décente et minimale. Vous pouvez utiliser un wrapper minimal autour d'un CSPRNG (défini comme
sysrandom
ci-dessous) pour amorcer le PRNG.les fenêtres
Vous pouvez compter sur
CryptGenRandom
un CSPRNG. Par exemple, vous pouvez utiliser le code suivant:Unix-like
Sur de nombreux systèmes de type Unix, vous devez utiliser / dev / urandom lorsque cela est possible (bien que cela ne soit pas garanti sur les systèmes compatibles POSIX).
Autre
Si aucun CSPRNG n'est disponible, vous pouvez choisir de vous fier à
std::random_device
. Cependant, j'éviterais cela si possible, car divers compilateurs (notamment MinGW) l'implémentent avec un PRNG (en fait, produisant la même séquence à chaque fois pour alerter les humains que ce n'est pas correctement aléatoire).Semis
Maintenant que nous avons nos pièces avec une surcharge minimale, nous pouvons générer les bits d'entropie aléatoire souhaités pour amorcer notre PRNG. L'exemple utilise (évidemment insuffisant) 32 bits pour amorcer le PRNG, et vous devez augmenter cette valeur (qui dépend de votre CSPRNG).
Comparaison pour booster
Nous pouvons voir des parallèles pour boost :: random_device (un vrai CSPRNG) après un rapide coup d'œil au code source . Boost utilise
MS_DEF_PROV
Windows, qui est le type de fournisseur pourPROV_RSA_FULL
. La seule chose qui manque serait de vérifier le contexte cryptographique, ce qui peut être fait avecCRYPT_VERIFYCONTEXT
. Sur * Nix, Boost utilise/dev/urandom
. IE, cette solution est portable, bien testée et facile à utiliser.Spécialisation Linux
Si vous êtes prêt à sacrifier la concision pour la sécurité,
getrandom
c'est un excellent choix sur Linux 3.17 et supérieur, et sur Solaris récent.getrandom
se comporte de la même manière que/dev/urandom
, sauf qu'il se bloque si le noyau n'a pas encore initialisé son CSPRNG après le démarrage. L'extrait de code suivant détecte si Linuxgetrandom
est disponible, et si ce n'est pas le cas, il revient à/dev/urandom
.OpenBSD
Il y a une dernière mise en garde: OpenBSD moderne n'en a pas
/dev/urandom
. Vous devriez utiliser getentropy à la place.d'autres pensées
Si vous avez besoin d'octets aléatoires sécurisés par cryptographie, vous devriez probablement remplacer le fstream par l'ouverture / lecture / fermeture sans tampon de POSIX. C'est parce que les deux
basic_filebuf
etFILE
contiennent un tampon interne, qui sera alloué via un allocateur standard (et donc pas effacé de la mémoire).Cela pourrait facilement être fait en changeant
sysrandom
en:Merci
Un merci spécial à Ben Voigt pour avoir signalé les
FILE
utilisations de lectures tamponnées et ne devraient donc pas être utilisées.Je voudrais également remercier Peter Cordes pour avoir mentionné
getrandom
, et le manque d'OpenBSD/dev/urandom
.la source
/dev/random
serait le meilleur choix pour semer un RNG, mais apparemment, il/dev/urandom
est toujours considéré comme sûr sur le plan informatique même s'il/dev/random
se bloquerait en raison de la faible entropie disponible, c'est doncurandom
le choix recommandé pour tout sauf peut-être les tampons ponctuels. Voir aussi unix.stackexchange.com/questions/324209/… . Méfiez-vous des graines prévisiblesurandom
très tôt après le démarrage, cependant.getrandom(2)
appel système de Linux est comme l'ouverture et la lecture/dev/urandom
, sauf qu'il bloquera si les sources aléatoires du noyau n'ont pas encore été initialisées. Je pense que cela vous évite le problème de l'aléatoire de faible qualité au démarrage précoce sans bloquer dans d'autres cas comme le/dev/random
fait./dev/urandom
fonctionne généralement. La discussion de la liste de diffusion Python à ce sujet est quelque chose à laquelle je m'abonne généralement: bugs.python.org/issue27266Dans un sens, cela ne peut pas être fait de manière portative. Autrement dit, on peut concevoir une plate-forme entièrement déterministe valide exécutant C ++ (par exemple, un simulateur qui marche l'horloge de la machine de manière déterministe, et avec des E / S "déterminées") dans laquelle il n'y a aucune source d'aléa pour amorcer un PRNG.
la source
std::random_device
serait dans cette catégorie, mais apparemment ce n'est pas le cas si certaines implémentations réelles utilisent un PRNG à graines fixes! Cela va bien au-delà de l'argument d'einpoklum.Vous pouvez utiliser a
std::seed_seq
et le remplir au moins à la taille d'état requise pour le générateur en utilisant la méthode d'Alexander Huszagh pour obtenir l'entropie:S'il y avait un moyen approprié de remplir ou de créer une SeedSequence à partir d'un UniformRandomBitGenerator dans la bibliothèque standard, utiliser
std::random_device
correctement pour l'amorçage serait beaucoup plus simple.la source
L'implémentation sur laquelle je travaille tire parti de la
state_size
propriété dumt19937
PRNG pour décider du nombre de graines à fournir lors de l'initialisation:Je pense qu'il y a place à amélioration parce que cela
std::random_device::result_type
pourrait différer de lastd::mt19937::result_type
taille et de la gamme, ce qui devrait vraiment être pris en compte.Une note sur std :: random_device .
Selon la
C++11(/14/17)
(les) norme (s):Cela signifie que l'implémentation ne peut générer des valeurs déterministes que si elle est empêchée de générer des valeurs non déterministes par une certaine limitation.
Le
MinGW
compilateur surWindows
ne fournit pas de valeurs non déterministes à partir de sonstd::random_device
, bien qu'elles soient facilement disponibles à partir du système d'exploitation. Je considère donc cela comme un bogue et peu probable comme un phénomène courant dans les implémentations et les plates-formes.la source
std::random_device
, et est donc vulnérable aux problèmes qui en découlent.std::random_device
? Je sais que la norme permet unePRNG
solution de secours, mais je pense que c'est juste pour se couvrir car il est difficile d'exiger que chaque appareil qui l'utiliseC++
ait une source aléatoire non déterministe. Et s'ils ne le font pas, que pourriez-vous faire à ce sujet de toute façon?std::random_device
. Je pense que c'est l'esprit de la norme. J'ai donc cherché et je ne peux trouverMinGW
que ce qui est cassé à cet égard. Personne ne semble signaler ce problème avec quoi que ce soit d'autre que j'ai trouvé. Donc, dans ma bibliothèque, j'ai simplement marquéMinGW
comme non pris en charge. S'il y avait un problème plus large, je le repenserais. Je ne vois tout simplement pas la preuve de cela pour le moment.std::random_device
pour tout le monde en le rendant disponible sous une forme qui ne fournit pas les capacités aléatoires de la plate-forme. Les implémentations de faible qualité vont à l'encontre de l'objectif de l'API existant. Il serait préférable que l'OMI ne la mette pas en œuvre du tout jusqu'à ce qu'elle fonctionne. (Ou mieux, si l'API fournissait un moyen de demander un échec si un caractère aléatoire de haute qualité n'était pas disponible, MinGW pourrait donc éviter de causer des risques de sécurité tout en donnant des graines différentes pour les jeux ou autre.)Il n'y a rien de mal à semer en utilisant le temps, en supposant que vous n'en ayez pas besoin pour être sécurisé (et vous n'avez pas dit que c'était nécessaire). L'idée est que vous pouvez utiliser le hachage pour corriger le caractère non aléatoire. J'ai trouvé que cela fonctionne correctement dans tous les cas, y compris et en particulier pour les simulations de Monte Carlo lourdes.
Une caractéristique intéressante de cette approche est qu'elle se généralise à l'initialisation à partir d'autres ensembles de graines pas vraiment aléatoires. Par exemple, si vous souhaitez que chaque thread ait son propre RNG (pour la sécurité des threads), vous pouvez simplement l'initialiser en fonction de l'ID de thread haché.
Ce qui suit est un SSCCE , distillé de ma base de code (pour plus de simplicité; certaines structures de support OO ont élidé):
la source
1
et2
et observez que la séquence de flottants générés par eux prend un certain temps à vraiment diverger).Voici ma propre tentative à la question:
L'idée ici est d'utiliser XOR pour combiner de nombreuses sources potentielles d'entropie (temps rapide, temps lent,
std::random-device
emplacements de variables statiques, emplacements de tas, emplacements de fonctions, emplacements de bibliothèque, valeurs spécifiques au programme) afin de tenter au mieux d'initialiser le mt19937. Tant qu'au moins une fois la source est "bonne", le résultat sera au moins aussi "bon".Cette réponse n'est pas aussi courte qu'il serait préférable et peut contenir une ou plusieurs erreurs de logique. Je considère donc que c'est un travail en cours. Veuillez commenter si vous avez des commentaires.
la source
&i ^ &myseed
devrait avoir beaucoup moins d'entropie que l'un ou l'autre seul, car les deux sont des objets avec une durée de stockage statique dans la même unité de traduction et donc susceptibles d'être assez proches les uns des autres. Et vous ne semblez pas réellement utiliser la valeur spéciale de l'initialisation demyseed
?^
est un horrible combinateur de hachage; si deux valeurs ont toutes les deux beaucoup d'entropie, mais peu comparées l'une à l'autre, cela la supprime.+
est généralement meilleur (car x + x ne brûle qu'un bit d'entropie dans x, tandis que x ^ x les brûle tous). La fonction n'est pas la sécurité, je soupçonne (rd()
)+
je veux dire sur non signé (+
sur signé est UB-appât). Bien que ce soient des cas UB quelque peu ridicules, vous avez dit portable./dev/urandom
ou/dev/random
).Ils sont disponibles sur les systèmes modernes de type UNIX, tels que Linux, Solaris et OpenBSD.
la source
Une plate-forme donnée peut avoir une source d'entropie, telle que
/dev/random
. Nanosecondes depuis l'époque avecstd::chrono::high_resolution_clock::now()
est probablement la meilleure graine de la bibliothèque standard.J'ai précédemment utilisé quelque chose comme
(uint64_t)( time(NULL)*CLOCKS_PER_SEC + clock() )
pour obtenir plus de bits d'entropie pour les applications qui ne sont pas critiques pour la sécurité.la source
/dev/urandom
, surtout dans un cas comme celui-ci./dev/random
blocs, et souvent sans bonnes raisons de le faire ([insérer une longue explication sur le nombre de systèmes d'exploitation différents qui estiment le caractère aléatoire des octets produits par / dev / random])./dev/urandom
n'existaient pas, et l'alternative au blocage était le déterminisme. Une boîte pourrait avoir/dev/hwrng
ou/dev/hw_random
aussi, ce qui devrait être encore mieux./dev/random
", et cela semble avoir déclenché une guerre sainte à propos de/dev/random
versus/dev/urandom
sur Linux que je n'avais pas l'intention quand j'ai donné cet exemple ..