Pourquoi utiliser la classe C # System.Random au lieu de System.Security.Cryptography.RandomNumberGenerator?

85

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?

Lernkurve
la source
7
Lequel préférez-vous taper?
Macha
13
Trop de gens l'utilisent sérieusement pour justifier ce qu'ils font (généralement pas à voix haute). Le code est lu plus qu'il n'est écrit, qui se soucie des différences de longueur insignifiantes?
Mark Sowul
3
Mais de toute façon, pourquoi devriez-vous utiliser des RNG cryptographiques si vous ne faites pas de cryptographie?
Mark Sowul
3
@Macha, c'est à ça que servent les alias ->using R = System.Security.Cryptography.RandomNumberGenerator; R.Create();
cchamberlain

Réponses:

144

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?

Kevin LaBranche
la source
30
J'aime beaucoup l'argument de l'intention.
Lernkurve
12
Il convient de noter que Random.GetNext est loin d'être bon pour «étaler» les nombres aléatoires sur le spectre, en particulier dans un environnement threadé. J'ai rencontré ce problème lors de l'écriture d'un programme pour tester différentes solutions au problème Rand7 from Rand5. Dans un test rapide de 100000 nombres aléatoires entre 0 et 10, 82470 des nombres générés étaient 0. J'ai vu des écarts similaires dans mes tests précédents. La cryptographie aléatoire est très uniforme dans sa distribution des nombres. Je suppose que la leçon est de toujours tester vos données aléatoires pour voir qu'elles sont «suffisamment aléatoires» pour vos besoins.
Kristoffer L
35
@Kristoffer Je pense que vous avez mal utilisé Random. Laissez-moi deviner: vous avez créé une nouvelle instance de la Randomclasse 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.
CodesInChaos
15
@CodesInChaos: En plus de cela, il existe une condition de concurrence Randomqui l'oblige à renvoyer tous les 0 lorsque le même objet est utilisé à partir de plusieurs threads.
BlueRaja - Danny Pflughoeft
3
@KristofferL: Voir le commentaire ci-dessus, voir aussi cette réponse
BlueRaja - Danny Pflughoeft
65

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,....
Henk Holterman
la source
2
Et il y a le BitConverter.ToInt32 (Byte [] value, int startIndex) qui peut être plus facile à comprendre. ;)
sisve
7
Ian Bell et David Braben ont utilisé un générateur aléatoire dans le jeu informatique Elite pour créer une vaste liste de planètes et leurs attributs (taille, etc.), avec une mémoire très limitée. Cela repose également sur le générateur créant un modèle déterministe (à partir d'une graine) - que celui de Crypto ne fournit évidemment pas (par conception.) Il y a plus d'informations sur la façon dont ils l'ont fait ici: wiki.alioth.net/index.php / Random_number_generator et le livre "Infinite Game Universe: Mathematical Techniques" ISBN: 1584500581 a une discussion plus générale sur ces techniques.
Daniel James Bryars
7
N'oubliez pas que MSDN ne garantit pas que cette propriété soit maintenue dans les versions .NET: «Il n'est pas garanti que l'implémentation du générateur de nombres aléatoires dans la classe Random reste la même dans les principales versions du .NET Framework».
Roman Starkov
2
@phoog "Par conséquent, votre code d'application ne doit pas supposer que la même valeur de départ entraînera la même séquence pseudo-aléatoire dans différentes versions de .NET Framework." - Je ne sais pas, me semble assez clair. Cependant, je ne serais pas surpris qu'ils ne puissent pas le changer dans la pratique sans casser les programmes existants, malgré cet avertissement.
Roman Starkov
2
@phoog: Vous dites une chose et ensuite exactement le contraire. Vous vous contredisez directement.
Timwi
53

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 Randommauvais pour des raisons non sécuritaires.

Mais je prétends que c'est le cas. L'implémentation .net 4 de Randomest 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 Randomcréé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 de Randomau 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 Randomavec 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ée

Il existe des paramètres pour lesquels ce Next(int maxValue)n'est clairement pas uniforme. Par exemple, si vous calculez, r.Next(1431655765) % 2vous obtiendrez 0environ 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 avec Next(). 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 que System.Security.Cryptography.RNGCryptoServiceProvider.GetBytessur 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);
}
CodesInChaos
la source
1
Intéressant, peut confirmer votre constat: sur ma machine Next (1431655765) donne également 2/3 avec tout ensemencement. Quelle est la magie du 1431655765? Comment êtes-vous arrivé à ce nombre?
citykid
1
@citykid Regardez le nombre en hexadécimal ou en bits. C'est la magie qui découle de la manière douteuse Randomutilisé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 ça randomValue * max / 2^{31}.
CodesInChaos
1431655765_10 = 1010101010101010101010101010101_2
Tim S.
6
Hm. Alors, quelle implémentation de Random pour C # recommandez-vous d'utiliser?
Arsen Zahray
1
Vache sacrée, la non-uniformité de la distribution de 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.)
Mark Amery
24

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.

Michael
la source
2
Merci de le démontrer avec un test réel.
Lernkurve
3
Vous pourriez penser que c'est dur, mais -1 pour afficher les résultats d'un benchmark de performance sans inclure le code du benchmark. Même si les caractéristiques de performance de Randomet RNGCryptoServiceProvidern'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.
Mark Amery
21

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/randomnoyau CPRNG pour toutes sortes de nombres aléatoires, alors que le comportement correct serait de lire une petite valeur de départ /dev/urandomet de l'utiliser pour semer leur propre PRNG.

Jörg W Mittag
la source
J'ai lu l'article de Wikipédia et d'autres sources Internet sur l'épuisement de l'entropie et de l'entropie, et je ne le comprends pas très bien. Comment puis-je épuiser le pool d'entropie lorsque le générateur de nombres aléatoires est alimenté en temps système, en nombre d'octets libres, etc.? Comment les autres peuvent-ils l'utiliser comme vecteur d'attaque pour prédire des nombres aléatoires? Pouvez-vous donner un exemple simple? Peut-être que cette discussion doit être mise hors ligne. en.wikipedia.org/wiki/Entropy_%28computing%29
Lernkurve
3
L'heure système n'est pas une source d'entropie, car elle est prévisible. Je ne suis pas sûr du nombre d'octets libres, mais je doute que ce soit une source d'entropie de haute qualité non plus. En envoyant plus de requêtes au serveur, l'attaquant peut faire diminuer le nombre d'octets libres, ce qui le rend partiellement déterministe. Votre application devient un vecteur d'attaque car en épuisant le pool d'entropie, elle oblige l'autre application critique pour la sécurité à utiliser des nombres aléatoires moins aléatoires - ou à attendre que la source d'entropie soit reconstituée.
quant_dev
Je comprends que si l'on a un générateur pseudo-aléatoire alimenté par exemple avec une graine 32 bits, une attaque par force brute sera souvent assez facile; même une graine 64 bits peut être sujette à des attaques d'anniversaire. Une fois que la graine devient beaucoup plus grosse que cela, cependant, je ne vois pas tout à fait le risque. Si l'on a un générateur aléatoire qui, pour chaque octet de sortie, passe un état de 128 bits à travers un algorithme de chiffrement par bloc, puis sort les 8 bits inférieurs, comment un attaquant, même avec des concerts d'octets de sortie consécutifs, peut-il déduire l'état, en l'absence de faiblesses dans l'algorithme de cryptage lui-même?
supercat du
11

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é.

Dan Diplo
la source
9

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
3
Je pense que vous vous trompez sur votre ampleur; l'envoi des données financières doit être extrêmement rapide; si votre algorithme de trading peut obtenir le résultat 0,1 ms plus rapidement que la concurrence, vous vous retrouvez mieux dans la file d'attente des commandes d'achat / vente / stop-loss / quote. 3 secondes est une éternité. C'est pourquoi les commerçants investissent dans des ordinateurs incroyablement bons. Voir la réponse précédente; Crypt.RNG ne prend que 0,0028 ms par nouveau nombre; 0,0000028 secondes, vous êtes donc décalé de 9 ordres de grandeur en termes de temps de traitement nécessaire et également de l'importance de la vitesse.
Henrik
4

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.

nos
la source
2

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.

Tvanfosson
la source
2

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.

quant_dev
la source
1
Si seulement System.Random faisait non plus .. oh, bien.
user2864740
2

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.Randomest 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.Randomremplacement 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.

Ben
la source
1

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

Jeux de Traderhut
la source
3
Ma première hypothèse est que vous avez recréé Randomtrop souvent. Il ne doit être créé qu'une seule fois en appelant Nextcette instance plusieurs fois. Randomest mauvais, mais pas si mal. Pouvez-vous publier un exemple de programme avec une paire de graines qui présente ce problème?
CodesInChaos
Le code créerait un Random () au début de chaque niveau (mais c'était un problème majeur avec le niveau 1 plus que les suivants) Le code était à peu près le suivant:
Traderhut Games
Rnd = new Random ((uint) GameSeed); NextGameSeed = Rnd.Next (2000000000); Chaque niveau utilisait un nouveau Aléatoire qui a été créé avec une nouvelle graine - La graine a été enregistrée pour chaque niveau afin que je puisse recréer la carte, et également confirmer la séquence de graines aléatoires correspondant. Cela me permet de confirmer que le jeu est une série valide de cartes qui ont été résolues et de recréer le jeu.
Traderhut Games
Et au départ, Random a été créé sur la base de System.DateTime.Now.Ticks (ou 0), puis le GameSeed a été choisi en utilisant le même appel que Rnd.Next () ci-dessus. Si je ne peux pas faire cela, il y a un problème sérieux avec l'ensemencement du générateur de nombres aléatoires.
Traderhut Games
ce n'est pas une réponse à la question initiale!
Mike Dinescu
-1

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.

citykid
la source