web-dev-qa-db-fra.com

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

Pourquoi utiliser le générateur de nombres aléatoires "standard" de System.Random au lieu d'utiliser toujours 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 peut-on ne pas toujours utiliser le générateur de nombres aléatoires sécurisé? Pourquoi utiliser System.Random? La performance peut-être?

77
Lernkurve

Vitesse et intention. Si vous générez un nombre aléatoire et n'avez pas besoin de sécurité, pourquoi utiliser une fonction de cryptographie 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?

133
klabranche

Outre la vitesse et l'interface la 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 lors des tests.

Random gen1 = new Random();     // auto seeded by the clock
Random gen2 = new Random(0);    // Next(10) always yields 7,8,7,5,2,....
63
Henk Holterman

Tout d'abord, la présentation que vous avez liée ne parle que de nombres aléatoires à des fins de sécurité. Il ne prétend donc pas que Random est mauvais à des fins non liées à la sécurité.

Mais je le prétends. 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.

Défaut 1: L'ensemencement

Le constructeur par défaut amorce 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". Ceci est particulièrement gênant si vous souhaitez multi-thread votre code, car vous ne pouvez pas simplement créer une instance de Random au début de l'exécution de chaque thread.

La solution de contournement est d'être extrêmement prudent lors de l'utilisation du constructeur par défaut et d'amorcer manuellement si nécessaire.

Un autre problème ici est que l'espace de départ 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 d'anniversaire ). L'ensemencement manuel n'est donc pas facile non plus.

Défaut 2: la distribution des nombres aléatoires retournés par Next(int maxValue) est biaisée

Il y a des paramètres pour lesquels Next(int maxValue) n'est clairement pas uniforme. Par exemple, si vous calculez r.Next(1431655765) % 2 vous obtiendrez 0 Dans environ 2/3 des échantillons. (Exemple de code à la fin de la réponse.)

Défaut 3: la méthode NextBytes() 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(). À partir de cela, je soupçonne qu'ils créent en effet un échantillon par octet.

Une meilleure implémentation en utilisant 3 octets sur chaque échantillon accélèrerait NextBytes() de près d'un facteur 3.

Grâce à cette faille, Random.NextBytes() n'est que 25% plus rapide que System.Security.Cryptography.RNGCryptoServiceProvider.GetBytes Sur ma machine (Win7, Core i3 2600MHz).

Je suis sûr que si quelqu'un inspectait le code source/décompilé, il trouverait encore plus de défauts que je n'en ai trouvé avec mon analyse de 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);
}
48
CodesInChaos

System.Random est beaucoup plus performant car il ne génère pas de nombres aléatoires sécurisés cryptographiquement.

Un test simple sur ma machine remplissant un tampon de 4 octets avec des données aléatoires 1 000 000 de fois prend 49 ms pour Random, mais 2845 ms pour RNGCryptoServiceProvider. Notez que si vous augmentez la taille de la mémoire tampon que vous remplissez, la différence se rétrécit car la surcharge de RNGCryptoServiceProvider est moins pertinente.

24
Michael

Les raisons les plus évidentes ont déjà été mentionnées, alors voici une raison 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, qui (selon la mise en œuvre du CPRNG) l'affaiblira (permettant ainsi à un attaquant de le prédire) ou il se bloquera tout en essayant de se remplir. son pool d'entropie (devenant ainsi un vecteur d'attaque pour une attaque DoS).

Quoi qu'il en soit, votre application est maintenant devenue un vecteur d'attaque pour d'autres applications totalement indépendantes qui, contrairement à la vôtre, sont en fait vitales dépendent des propriétés cryptographiques du CPRNG.

Il s'agit d'un problème réel dans le 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 la souris et le 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 de /dev/urandom et l'utiliser pour semer leur propre PRNG.

20
Jörg W Mittag

Si vous programmez un jeu de cartes ou un loteur en ligne, vous voudrez vous assurer que la séquence est presque impossible à deviner. Cependant, si vous montrez aux utilisateurs, par exemple, une citation du jour, les performances sont plus importantes que la sécurité.

11
Dan Diplo

Notez que la classe System.Random en C # est codée de manière incorrecte, donc doit être évitée.

https://connect.Microsoft.com/VisualStudio/feedback/details/634761/system-random-serious-bug#tabs

10
evolvedmicrobe

Cela a été longuement discuté, mais en fin de compte, la question de la performance est une considération secondaire lors de la sélection d'un RNG. Il existe une vaste gamme de RNGs, et le LCG Lehmer en conserve qui compose la plupart des RNGs du 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 aucune vraie raison de `` réinventer la roue '' dans ce cas, et B) pour quoi la grande majorité des gens l'utiliseront, 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 RNG Lehmer 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ù des prix réels sont impliqués 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 à faire un cycle au mieux. Ces jours-ci, c'est une porte ouverte à une attaque par force brute. Dans un cas comme celui-ci, le développeur voudra aller à quelque chose comme un Very Long Period RNG 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 Mersenne Twister, ou Générateur récursif multiple d'une certaine 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 elle 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 chasseurs et des hélicoptères était financièrement faisable, ce serait la méthode de choix. Dans un cas comme celui-ci, l'utilisation d'un RNG cryptographiquement fort est logique, car quel que soit le niveau de sécurité que vous pouvez obtenir, ce n'est pas autant que vous le souhaitez. Donc, vous en prendrez autant que vous pourrez en trouver, et le coût est un problème de deuxième place très, très éloigné, soit en temps, soit en argent. Et si cela signifie que chaque séquence aléatoire prend 3 secondes à générer sur un ordinateur très puissant, vous allez attendre les 3 secondes, car dans le schéma des choses, c'est un coût trivial.

9
David

Tout le monde n'a pas besoin de nombres aléatoires sécurisés cryptographiquement, et ils pourraient bénéficier davantage d'un prng simple plus rapide. Peut-être plus important encore, vous pouvez contrôler la séquence des numéros System.Random.

Dans une simulation utilisant des nombres aléatoires que vous pourriez vouloir recréer, vous réexécutez la simulation avec la même graine. Il peut être utile pour suivre les bogues lorsque vous souhaitez également régénérer un scénario défectueux donné - exécuter votre programme avec la même séquence exacte de nombres aléatoires qui a bloqué le programme.

4
nos

Si je n'ai pas besoin de la sécurité, c'est-à-dire que je veux juste une valeur relativement indéterminée pas une valeur cryptographiquement solide, Random a une interface beaucoup plus facile à utiliser.

2
tvanfosson

Des besoins différents nécessitent des RNG différents. Pour la crypto, vous voulez que vos nombres aléatoires soient aussi aléatoires que possible. Pour les simulations de Monte-Carlo, vous voulez qu'elles remplissent l'espace de manière uniforme et qu'elles puissent démarrer le RNG à partir d'un état connu.

2
quant_dev

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éterministes, qui prend son nom pour des raisons historiques.

La raison d'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 voulez améliorer le "caractère aléatoire" sans sacrifier l'interface, vous pouvez hériter de System.Random écraser plusieurs méthodes.

Pourquoi voudriez-vous une séquence déterministe

Une raison d'avoir une séquence déterministe plutôt que le vrai hasard est parce 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 la simulation exactement la même chose, par ex. à des fins de débogage, vous pouvez le faire en initialisant la séquence à la place avec la valeur enregistré.

Pourquoi voudriez-vous cette séquence particulière, pas très bonne

La seule raison à laquelle je peux penser serait pour la compatibilité descendante avec le code existant qui utilise cette classe.

En bref, si vous souhaitez améliorer la séquence sans changer le reste de votre code, allez-y.

2
Ben