Pourquoi quelqu'un utiliserait-il le générateur de nombres aléatoires "standard" de System.Random au lieu de toujours utiliser le générateur de nombres aléatoires cryptographiquement sécurisé de System.Security.Cryptography.RandomNumberGenerator (ou ses sous-classes parce que RandomNumberGenerator est abstrait)?
Nate Lawson nous dit dans sa présentation Google Tech Talk " Crypto Strikes Back " à la minute 13:11 de ne pas utiliser les générateurs de nombres aléatoires "standard" de Python, Java et C # et d'utiliser à la place la version cryptographiquement sécurisée.
Je connais la différence entre les deux versions de générateurs de nombres aléatoires (voir question 101337 ).
Mais quelle raison y at-il pour ne pas toujours utiliser le générateur de nombres aléatoires sécurisé? Pourquoi utiliser System.Random? La performance peut-être?
la source
using R = System.Security.Cryptography.RandomNumberGenerator; R.Create();
Réponses:
Rapidité et intention. Si vous générez un nombre aléatoire et n'avez pas besoin de sécurité, pourquoi utiliser une fonction de cryptage lente? Vous n'avez pas besoin de sécurité, alors pourquoi faire penser à quelqu'un d'autre que le numéro peut être utilisé pour quelque chose de sécurisé alors qu'il ne le sera pas?
la source
Random
. Laissez-moi deviner: vous avez créé une nouvelle instance de laRandom
classe pour chaque nombre, qui, étant donné qu'elle est amorcée par une minuterie grossière, sera amorcée avec la même valeur pendant un intervalle d'environ 1 à 16 ms.Random
qui l'oblige à renvoyer tous les 0 lorsque le même objet est utilisé à partir de plusieurs threads.Outre la vitesse et l'interface plus utile (
NextDouble()
etc.), il est également possible de créer une séquence aléatoire répétable en utilisant une valeur de départ fixe. C'est très utile, entre autres pendant les tests.Random gen1 = new Random(); // auto seeded by the clock Random gen2 = new Random(0); // Next(10) always yields 7,8,7,5,2,....
la source
Tout d'abord, la présentation que vous avez liée ne parle que de nombres aléatoires pour des raisons de sécurité. Donc, il ne prétend pas être
Random
mauvais pour des raisons non sécuritaires.Mais je prétends que c'est le cas. L'implémentation .net 4 de
Random
est défectueuse de plusieurs manières. Je recommande de ne l'utiliser que si vous ne vous souciez pas de la qualité de vos nombres aléatoires. Je recommande d'utiliser de meilleures implémentations tierces.Faille 1: l'ensemencement
Le constructeur par défaut amorce avec l'heure actuelle. Ainsi, toutes les instances de
Random
créées avec le constructeur par défaut dans un court laps de temps (environ 10 ms) renvoient la même séquence. Ceci est documenté et «par conception». C'est particulièrement ennuyeux si vous voulez multi-threader votre code, car vous ne pouvez pas simplement créer une instance deRandom
au début de l'exécution de chaque thread.La solution de contournement consiste à faire très attention lorsque vous utilisez le constructeur par défaut et à amorcer manuellement si nécessaire.
Un autre problème ici est que l'espace d'amorçage est plutôt petit (31 bits). Donc, si vous générez 50k instances de
Random
avec des graines parfaitement aléatoires, vous obtiendrez probablement une séquence de nombres aléatoires deux fois (en raison du paradoxe de l' anniversaire ). Le semis manuel n'est donc pas non plus facile à réaliser.Faille 2: la distribution des nombres aléatoires renvoyés par
Next(int maxValue)
est biaiséeIl existe des paramètres pour lesquels ce
Next(int maxValue)
n'est clairement pas uniforme. Par exemple, si vous calculez,r.Next(1431655765) % 2
vous obtiendrez0
environ 2/3 des échantillons. (Exemple de code à la fin de la réponse.)Défaut 3: la
NextBytes()
méthode est inefficace.Le coût par octet de
NextBytes()
est à peu près aussi élevé que le coût de génération d'un échantillon entier complet avecNext()
. De cela, je soupçonne qu'ils créent en effet un échantillon par octet.Une meilleure implémentation utilisant 3 octets sur chaque échantillon accélérerait
NextBytes()
de près d'un facteur 3.Merci à cette faille
Random.NextBytes()
est seulement environ 25% plus rapide queSystem.Security.Cryptography.RNGCryptoServiceProvider.GetBytes
sur ma machine (Win7, Core i3 2600MHz).Je suis sûr que si quelqu'un inspectait le code d'octet source / décompilé, il trouverait encore plus de défauts que ce que j'ai trouvé avec mon analyse de la boîte noire.
Exemples de code
r.Next(0x55555555) % 2
est fortement biaisé:Random r = new Random(); const int mod = 2; int[] hist = new int[mod]; for(int i = 0; i < 10000000; i++) { int num = r.Next(0x55555555); int num2 = num % 2; hist[num2]++; } for(int i=0;i<mod;i++) Console.WriteLine(hist[i]);
Performance:
byte[] bytes=new byte[8*1024]; var cr=new System.Security.Cryptography.RNGCryptoServiceProvider(); Random r=new Random(); // Random.NextBytes for(int i=0;i<100000;i++) { r.NextBytes(bytes); } //One sample per byte for(int i=0;i<100000;i++) { for(int j=0;j<bytes.Length;j++) bytes[j]=(byte)r.Next(); } //One sample per 3 bytes for(int i=0;i<100000;i++) { for(int j=0;j+2<bytes.Length;j+=3) { int num=r.Next(); bytes[j+2]=(byte)(num>>16); bytes[j+1]=(byte)(num>>8); bytes[j]=(byte)num; } //Yes I know I'm not handling the last few bytes, but that won't have a noticeable impact on performance } //Crypto for(int i=0;i<100000;i++) { cr.GetBytes(bytes); }
la source
Random
utilisée pour transformer un entier de 31 bits en un nombre avec la limite supérieure spécifiée. J'ai oublié les détails, mais c'est quelque chose comme çarandomValue * max / 2^{31}
.Next()
, démontrée par vous ici, est un bug assez spectaculaire - et toujours présent aujourd'hui, 6 ans après que vous ayez rédigé vos résultats pour la première fois. (Je dis "bug" plutôt que simplement "faille", car la documentation prétend que "les nombres pseudo-aléatoires sont choisis avec une probabilité égale à partir d'un ensemble fini de nombres" . Ce n'est pas le cas, et votre code le prouve.)System.Random est beaucoup plus performant car il ne génère pas de nombres aléatoires cryptographiquement sécurisés.
Un simple test sur ma machine remplissant un tampon de 4 octets avec des données aléatoires 1 000 000 fois prend 49 ms pour Random, mais 2845 ms pour RNGCryptoServiceProvider. Notez que si vous augmentez la taille du tampon que vous remplissez, la différence se rétrécit car la surcharge de RNGCryptoServiceProvider est moins pertinente.
la source
Random
etRNGCryptoServiceProvider
n'ont pas changé au cours des 8 dernières années (ce que je sais qu'elles auraient pu avoir), j'ai vu suffisamment de benchmarks complètement cassés utilisés sur Stack Overflow pour ne pas faire confiance aux résultats d'un benchmark dont le code n'est pas accessible au public.Les raisons les plus évidentes ont déjà été mentionnées, donc en voici une plus obscure: les PRNG cryptographiques doivent généralement être continuellement réensemencés avec une entropie «réelle». Ainsi, si vous utilisez un CPRNG trop souvent, vous pourriez épuiser le pool d'entropie du système, ce qui (en fonction de l'implémentation du CPRNG) l'affaiblira (permettant ainsi à un attaquant de le prédire) ou il se bloquera en essayant de se remplir son pool d'entropie (devenant ainsi un vecteur d'attaque pour une attaque DoS).
Dans tous les cas, votre application est maintenant devenue un vecteur d'attaque pour d'autres applications totalement indépendantes qui - contrairement à la vôtre - dépendent en fait de manière vitale en des propriétés cryptographiques du CPRNG.
C'est un problème réel du monde réel, BTW, qui a été observé sur des serveurs sans tête (qui ont naturellement des pools d'entropie plutôt petits car ils manquent de sources d'entropie telles que l'entrée de la souris et du clavier) exécutant Linux, où les applications utilisent incorrectement le
/dev/random
noyau CPRNG pour toutes sortes de nombres aléatoires, alors que le comportement correct serait de lire une petite valeur de départ/dev/urandom
et de l'utiliser pour semer leur propre PRNG.la source
Si vous programmez un jeu de cartes ou une loterie en ligne, vous devez vous assurer que la séquence est pratiquement impossible à deviner. Cependant, si vous montrez aux utilisateurs, par exemple, une citation du jour, les performances sont plus importantes que la sécurité.
la source
Cela a été longuement discuté, mais finalement, la question des performances est une considération secondaire lors du choix d'un RNG. Il existe une vaste gamme de RNG, et le Lehmer LCG en conserve qui compose la plupart des RNG système n'est pas le meilleur ni même nécessairement le plus rapide. Sur les anciens systèmes lents, c'était un excellent compromis. Ce compromis est rarement vraiment pertinent de nos jours. La chose persiste dans les systèmes actuels principalement parce que A) la chose est déjà construite, et il n'y a pas de vraie raison de `` réinventer la roue '' dans ce cas, et B) pour ce que la grande majorité des gens l'utilisera, c'est 'assez bien'.
En fin de compte, la sélection d'un RNG se résume au rapport risque / récompense. Dans certaines applications, par exemple un jeu vidéo, il n'y a aucun risque. Un Lehmer RNG est plus que suffisant et est petit, concis, rapide, bien compris et «dans la boîte».
Si l'application est, par exemple, un jeu de poker en ligne ou une loterie où il y a des prix réels et de l'argent réel entre en jeu à un moment donné de l'équation, le Lehmer «dans la boîte» n'est plus adéquat. Dans une version 32 bits, il n'a que 2 ^ 32 états valides possibles avant de commencer à fonctionner au mieux . De nos jours, c'est une porte ouverte à une attaque par force brute. Dans un cas comme celui-ci, le développeur voudra aller vers quelque chose comme un RNG à très longue période de certaines espèces, et probablement le semer à partir d'un fournisseur cryptographiquement fort. Cela donne un bon compromis entre vitesse et sécurité. Dans un tel cas, la personne recherchera quelque chose comme le Mersenne Twister , ou un générateur récursif multiple de quelque sorte.
Si l'application est quelque chose comme la communication de grandes quantités d'informations financières sur un réseau, il y a maintenant un risque énorme, et il surpasse largement toute récompense possible. Il y a encore des voitures blindées parce que parfois des hommes lourdement armés sont la seule sécurité qui soit adéquate, et croyez-moi, si une brigade d'opérations spéciales avec des chars, des combattants et des hélicoptères était financièrement réalisable, ce serait la méthode de choix. Dans un cas comme celui-ci, utiliser un RNG cryptographiquement fort a du sens, car quel que soit le niveau de sécurité que vous pouvez obtenir, ce n'est pas autant que vous le souhaitez. Vous en prendrez donc tout ce que vous pouvez trouver, et le coût est un problème de deuxième place très, très éloigné, que ce soit en temps ou en argent. Et si cela signifie que chaque séquence aléatoire prend 3 secondes à se générer sur un ordinateur très puissant, vous allez attendre les 3 secondes,
la source
Notez que la classe System.Random en C # est codée de manière incorrecte et doit donc être évitée.
https://connect.microsoft.com/VisualStudio/feedback/details/634761/system-random-serious-bug#tabs
la source
Tout le monde n'a pas besoin de numéros aléatoires sécurisés par cryptographie, et ils pourraient bénéficier davantage d'une configuration simple plus rapide. Le plus important est peut-être que vous pouvez contrôler la séquence des numéros System.Random.
Dans une simulation utilisant des nombres aléatoires que vous souhaiterez peut-être recréer, vous réexécutez la simulation avec la même valeur de départ. Cela peut être pratique pour suivre les bogues lorsque vous souhaitez également régénérer un scénario défectueux donné - exécuter votre programme avec exactement la même séquence de nombres aléatoires qui a planté le programme.
la source
Si je n'ai pas besoin de la sécurité, c'est-à-dire que je veux juste une valeur relativement indéterminée et non une valeur cryptographique forte, Random a une interface beaucoup plus facile à utiliser.
la source
Des besoins différents nécessitent différents RNG. Pour la cryptographie, vous voulez que vos nombres aléatoires soient aussi aléatoires que possible. Pour les simulations Monte Carlo, vous voulez qu'elles remplissent l'espace uniformément et qu'elles puissent démarrer le RNG à partir d'un état connu.
la source
Random
n'est pas un générateur de nombres aléatoires, c'est un générateur de séquences pseudo-aléatoires déterministe, qui prend son nom pour des raisons historiques.La raison à utiliser
System.Random
est si vous voulez ces propriétés, à savoir une séquence déterministe, qui est garantie de produire la même séquence de résultats lorsqu'elle est initialisée avec la même graine.Si vous souhaitez améliorer le "caractère aléatoire" sans sacrifier l'interface, vous pouvez hériter du
System.Random
remplacement de plusieurs méthodes.Pourquoi voudriez-vous une séquence déterministe
Une des raisons d'avoir une séquence déterministe plutôt qu'un vrai hasard est qu'elle est répétable.
Par exemple, si vous exécutez une simulation numérique, vous pouvez initialiser la séquence avec un (vrai) nombre aléatoire et enregistrer le nombre utilisé .
Ensuite, si vous souhaitez répéter exactement la même simulation, par exemple à des fins de débogage, vous pouvez le faire en initialisant à la place la séquence avec la valeur enregistrée .
Pourquoi voudriez-vous cette séquence particulière, pas très bonne
La seule raison à laquelle je peux penser serait la rétrocompatibilité avec le code existant qui utilise cette classe.
Bref, si vous souhaitez améliorer la séquence sans changer le reste de votre code, allez-y.
la source
J'ai écrit un jeu (Crystal Sliders sur l'iPhone: ici ) qui mettrait en place une série "aléatoire" de gemmes (images) sur la carte et vous faisiez pivoter la carte comme vous le vouliez et les sélectionniez et ils s'en allaient. - Similaire à Bejeweled. J'utilisais Random (), et il a été semé avec le nombre de tiques de 100ns depuis le démarrage du téléphone, une graine assez aléatoire.
J'ai trouvé ça incroyable que cela génère des jeux presque identiques les uns aux autres - sur les quelque 90 gemmes, de 2 couleurs, j'en aurais exactement deux à part 1 à 3 gemmes! Si vous lancez 90 pièces et obtenez le même motif à l'exception de 1 à 3 flips, c'est TRÈS improbable! J'ai plusieurs captures d'écran qui leur montrent la même chose. J'ai été choqué de voir à quel point System.Random () était mauvais! J'ai supposé que je devais avoir écrit quelque chose d'horriblement mal dans mon code et que je l'utilisais mal. J'avais tort cependant, c'était le générateur.
En tant qu'expérience - et solution finale, je suis retourné au générateur de nombres aléatoires que j'utilise depuis 1985 environ - qui est VASTEMENT meilleur. Il est plus rapide, a une période de 1,3 * 10 ^ 154 (2 ^ 521) avant de se répéter. L'algorithme d'origine a été semé avec un nombre de 16 bits, mais j'ai changé cela en un nombre de 32 bits et amélioré l'amorçage initial.
L'original est ici:
ftp://ftp.grnet.gr/pub/lang/algorithms/c/jpl-c/random.c
Au fil des ans, j'ai lancé tous les tests de nombres aléatoires auxquels je pouvais penser, et cela les a tous dépassés. Je ne m'attends pas à ce qu'il ait une quelconque valeur en tant que cryptographique, mais il renvoie un nombre aussi vite que "return * p ++;" jusqu'à ce qu'il soit à court des 521 bits, puis il exécute un processus rapide sur les bits pour en créer de nouveaux aléatoires.
J'ai créé un wrapper C # - appelé JPLRandom () a implémenté la même interface que Random () et changé tous les endroits où je l'ai appelé dans le code.
La différence était TRÈS meilleure - OMG j'étais étonné - il ne devrait y avoir aucun moyen que je puisse dire en regardant simplement les écrans d'environ 90 gemmes dans un modèle, mais j'ai fait une sortie d'urgence de mon jeu après cela.
Et je n'utiliserais plus jamais System.Random () pour quoi que ce soit. Je suis choqué que leur version soit époustouflée par quelque chose qui a maintenant 30 ans!
-Jeux de Traderhut
la source
Random
trop souvent. Il ne doit être créé qu'une seule fois en appelantNext
cette instance plusieurs fois.Random
est mauvais, mais pas si mal. Pouvez-vous publier un exemple de programme avec une paire de graines qui présente ce problème?Puisque System.Random est critiqué ici pour son "inexactitude" et son biais, je me suis vérifié.
Distribution
Ce code f # démontre qu'il se comporte vraiment bien - sur ma machine moyenne:
let r = System.Random() Seq.init 1000000 (fun _ -> r.Next(0,10)) |> Seq.toList |> Seq.groupBy id |> Seq.map (fun (v,ls) -> v, ls |> Seq.length) |> Seq.sortBy fst |> Seq.iter (printfn "%A") (0, 100208) (1, 99744) (2, 99929) (3, 99827) (4, 100273) (5, 100280) (6, 100041) (7, 100001) (8, 100175) (9, 99522)
Les versions du framework, la machine, le système d'exploitation peuvent tous faire la différence. Entrez le code en F # interactif sur votre machine et essayez vous-même. Pour la cyrptographie j'ai lu
let arr = [| 0uy |] let rr = System. Security.Cryptography.RandomNumberGenerator.Create() Seq.init 1000000 (fun _ -> rr.GetBytes(arr); arr.[0]) |> Seq.toList |> Seq.groupBy id |> Seq.map (fun (v,ls) -> v, ls |> Seq.length) |> Seq.sortBy fst |> Seq.take 10 // show first 10 bytes |> Seq.iter (printfn "%A") // distribution of first 10 bytes (0uy, 3862) (1uy, 3888) (2uy, 3921) (3uy, 3926) (4uy, 3948) (5uy, 3889) (6uy, 3922) (7uy, 3797) (8uy, 3861) (9uy, 3874)
performance
#time let arr = [| 0uy |] let r = System.Random() Seq.init 1000000 (fun _ -> r.NextBytes(arr); arr.[0] |> int64) |> Seq.sum Real: 00:00:00.204, CPU: 00:00:00.203, GC gen0: 45, gen1: 1, gen2: 1 val it : int64 = 127503467L let rr = System. Security.Cryptography.RandomNumberGenerator.Create() Seq.init 1000000 (fun _ -> rr.GetBytes(arr); arr.[0] |> int64) |> Seq.sum Real: 00:00:00.365, CPU: 00:00:00.359, GC gen0: 44, gen1: 0, gen2: 0 val it : int64 = 127460809L
ce qui suggère une relation 1: 2 et un comportement mémoire un peu plus agréable de la version crypto.
conclusion
Principalement pour son API beaucoup plus agréable, un peu pour ses performances et une assez bonne distribution, System.Random est préféré. System.Random peut également réduire les dépendances de bibliothèque et si un framework est porté, System.Random sera probablement disponible avant la variante Crypto.
la source