web-dev-qa-db-fra.com

RNGCryptoServiceProvider - Examen de nombres aléatoires

Tout en recherchant les meilleures tentatives pour générer des nombres vraiment aléatoires, je suis tombé sur cet exemple de code.

Vous cherchez des opinions sur cet extrait.

using System;
using System.Security.Cryptography;

private static int NextInt(int min, int max)
{
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] buffer = new byte[4];

    rng.GetBytes(buffer);
    int result = BitConverter.ToInt32(buffer, 0);

    return new Random(result).Next(min, max);
}

Source: http://www.vcskicks.com/code-snippet/rng-int.php

Est-ce que cela serait préférable à l’utilisation d’une graine de tique telle que

Random Rand = new Random(Environment.TickCount); 
Rand.Next(min, max);

Note:

Je ne recherche pas de fournisseurs de données aléatoires tiers, tels que Random.org , car une telle dépendance n'est pas réaliste pour l'application.

22
cweston

Eh bien, utiliser RNGCryptoServiceProvider vous donne une valeur de départ indéniable en crypto-force alors que Environment.TickCount est, en théorie, prévisible.

Une autre différence cruciale apparaîtrait lorsque vous appelez votre méthode NextInt plusieurs fois à la suite. Utiliser RNGCryptoServiceProvider ensemencera l'objet Random avec un numéro de crypto-force différent à chaque fois, ce qui signifie qu'il retournera un numéro aléatoire différent pour chaque appel. L'utilisation de TickCount risque d'ensemencer l'objet Random avec le même numéro à chaque fois (si la méthode est appelée plusieurs fois au cours d'une même "tick"), ce qui signifie qu'elle retournera le même nombre (supposé aléatoire) pour chaque appel.

Si vous avez réellement besoin de véritablement nombres aléatoires, vous ne devriez pas du tout utiliser un ordinateur pour les générer: vous devriez mesurer la désintégration radioactive ou quelque chose de similaire, véritablement imprévisible.

18
LukeH

N'utilisez pas votre code. Votre solution est fausse et génère de mauvais nombres aléatoires. Je suggère ma solution, qui génère des nombres aléatoires cryptographiquement forts:

public class SecureRandom : RandomNumberGenerator
{
    private readonly RandomNumberGenerator rng = new RNGCryptoServiceProvider();


    public int Next()
    {
        var data = new byte[sizeof(int)];
        rng.GetBytes(data);
        return BitConverter.ToInt32(data, 0) & (int.MaxValue - 1);
    }

    public int Next(int maxValue)
    {
        return Next(0, maxValue);
    }

    public int Next(int minValue, int maxValue)
    {
        if (minValue > maxValue)
        {
            throw new ArgumentOutOfRangeException();
        }
        return (int)Math.Floor((minValue + ((double)maxValue - minValue) * NextDouble()));
    }

    public double NextDouble()
    {
        var data = new byte[sizeof(uint)];
        rng.GetBytes(data);
        var randUint = BitConverter.ToUInt32(data, 0);
        return randUint / (uint.MaxValue + 1.0);
    }

    public override void GetBytes(byte[] data)
    {
        rng.GetBytes(data);
    }

    public override void GetNonZeroBytes(byte[] data)
    {
        rng.GetNonZeroBytes(data);
    }
}
5
Eldar Agalarov

J'ai posé une question similaire 2 ans en arrière :) vérifier et voir si cela vous aide. J'ai utilisé ce code pour générer un nombre aléatoire sécurisé pour le traitement des paiements.

5
Shoban

Je pense que ceci est un générateur plus efficace, et peut-être plus rapide que ceux énumérés ci-dessus.

public static class SecureRandom
{
    #region Constants
    private const int INT_SIZE = 4;
    private const int INT64_SIZE = 8;
    #endregion

    #region Fields
    private static RandomNumberGenerator _Random;
    #endregion

    #region Constructor
    static SecureRandom()
    {
        _Random = new RNGCryptoServiceProvider();
    }
    #endregion

    #region Random Int32
    /// <summary>
    /// Get the next random integer
    /// </summary>
    /// <returns>Random [Int32]</returns>
    public static Int32 Next()
    {
        byte[] data = new byte[INT_SIZE];
        Int32[] result = new Int32[1];

        _Random.GetBytes(data);
        Buffer.BlockCopy(data, 0, result, 0, INT_SIZE);

        return result[0];
    }

    /// <summary>
    /// Get the next random integer to a maximum value
    /// </summary>
    /// <param name="MaxValue">Maximum value</param>
    /// <returns>Random [Int32]</returns>
    public static Int32 Next(Int32 MaxValue)
    {
        Int32 result = 0;

        do
        {
            result = Next();
        } while (result > MaxValue);

        return result;
    }
    #endregion

    #region Random UInt32
    /// <summary>
    /// Get the next random unsigned integer
    /// </summary>
    /// <returns>Random [UInt32]</returns>
    public static UInt32 NextUInt()
    {
        byte[] data = new byte[INT_SIZE];
        Int32[] result = new Int32[1];

        do
        {
            _Random.GetBytes(data);
            Buffer.BlockCopy(data, 0, result, 0, INT_SIZE);
        } while (result[0] < 0);

        return (UInt32)result[0];
    }

    /// <summary>
    /// Get the next random unsigned integer to a maximum value
    /// </summary>
    /// <param name="MaxValue">Maximum value</param>
    /// <returns>Random [UInt32]</returns>
    public static UInt32 NextUInt(UInt32 MaxValue)
    {
        UInt32 result = 0;

        do
        {
            result = NextUInt();
        } while (result > MaxValue);

        return result;
    }
    #endregion

    #region Random Int64
    /// <summary>
    /// Get the next random integer
    /// </summary>
    /// <returns>Random [Int32]</returns>
    public static Int64 NextLong()
    {
        byte[] data = new byte[INT64_SIZE];
        Int64[] result = new Int64[1];

        _Random.GetBytes(data);
        Buffer.BlockCopy(data, 0, result, 0, INT64_SIZE);

        return result[0];
    }

    /// <summary>
    /// Get the next random unsigned long to a maximum value
    /// </summary>
    /// <param name="MaxValue">Maximum value</param>
    /// <returns>Random [UInt64]</returns>
    public static Int64 NextLong(Int64 MaxValue)
    {
        Int64 result = 0;

        do
        {
            result = NextLong();
        } while (result > MaxValue);

        return result;
    }
    #endregion

    #region Random UInt32
    /// <summary>
    /// Get the next random unsigned long
    /// </summary>
    /// <returns>Random [UInt64]</returns>
    public static UInt64 NextULong()
    {
        byte[] data = new byte[INT64_SIZE];
        Int64[] result = new Int64[1];

        do
        {
            _Random.GetBytes(data);
            Buffer.BlockCopy(data, 0, result, 0, INT64_SIZE);
        } while (result[0] < 0);

        return (UInt64)result[0];
    }

    /// <summary>
    /// Get the next random unsigned long to a maximum value
    /// </summary>
    /// <param name="MaxValue">Maximum value</param>
    /// <returns>Random [UInt64]</returns>
    public static UInt64 NextULong(UInt64 MaxValue)
    {
        UInt64 result = 0;

        do
        {
            result = NextULong();
        } while (result > MaxValue);

        return result;
    }
    #endregion

    #region Random Bytes
    /// <summary>
    /// Get random bytes
    /// </summary>
    /// <param name="data">Random [byte array]</param>
    public static byte[] NextBytes(long Size)
    {
        byte[] data = new byte[Size];
        _Random.GetBytes(data);
        return data;
    }
    #endregion
}
2
JGU

Je ne suggère vraiment pas d'utiliser l'exemple fourni. Bien que RNGCryptoServiceProvider retourne une valeur vraiment bonne (ou du moins il le devrait), il n'en va pas de même pour Random. De plus, on ne sait pas si Random (valeur) crée une véritable bijection par rapport à une valeur réaccordée par Next (..). De plus, il n'est pas garanti que Next (min, max) renvoie la valeur de manière réellement aléatoire (ce qui signifie que le nombre a autant de chances d'atteindre chaque valeur).

Je voudrais d'abord résoudre le problème pour obtenir le nombre dans l'intervalle 0 - max (exclusif). Ensuite, j'utiliserais la puissance de 2 la plus proche pour obtenir une valeur aléatoire dans la plage 0 - (2 ^ n - 1). Maintenant, une chose que vous NE DEVEZ JAMAIS faire ici consiste à utiliser modulo pour obtenir un nombre compris dans la plage préférée, telle que Rand (0 - (2 ^ n - 1))% max.

Exemple - max = 3, n = 2 (0 - (2 ^ 2 - 1))% 2, nombres (0, 1, 2, 3), valeurs correspondantes après modulo (0, 1, 2, 0). Voir que nous avons frappé 0 deux fois ce qui est vraiment mauvais au hasard.

La solution consisterait donc à utiliser crypto random pour obtenir une valeur égale à la puissance la plus proche de deux. Si la valeur est hors de la plage maximale, répétez proceudre (obtenez une autre crypto aléatoire) jusqu'à ce que la valeur se situe dans la plage donnée. Ce serait bien meilleur algorithme.

2
0xDEAD BEEF

Cela dépend vraiment de l'utilisation prévue ou de l'exigence du nombre aléatoire généré.

La classe Random est utile pour la randomisation pratique, telle que la randomisation de l'ordre d'affichage des images dans un rotateur d'image ou les rouleaux d'un dé.
Si, par contre, vous avez besoin de nombres aléatoires nécessitant une sécurité accrue, comme pour générer un mot de passe ou une clé de confirmation de paiement, utilisez une classe telle que - RNGCryptoServiceProvider ou créez votre propre implémentation de la classe abstraite RandomNumberGenerator qui implémente un algorithme cryptographique sont de meilleures alternatives.

1
nybbler

Ok, donc je suis un peu en retard pour la fête, mais je voulais vraiment une implémentation complète de System.Random qui puisse être appelée plusieurs fois au cours du même tic de la minuterie et donner des résultats différents. Après de nombreuses angoisses concernant différentes implémentations, j'ai opté pour la plus simple que j'ai proposée, qui fournit un constructeur par défaut fournissant une clé aléatoire au constructeur de base System.Random:

/// <summary> An implementation of System.Random whose default constructor uses a random seed value rather than the system time. </summary>
public class RandomEx : Random
{
    /// <summary> Initializes a new CryptoRandom instance using a random seed value. </summary>
    public RandomEx()
        : base(_GetSeed())
    { }

    /// <summary> Initializes a new CryptoRandom instance using the specified seed value. </summary>
    /// <param name="seed"> The seed value. </param>
    public RandomEx(int seed)
        : base(seed)
    { }

    // The static (shared by all callers!) RandomNumberGenerator instance
    private static RandomNumberGenerator _rng = null;

    /// <summary> Static method that returns a random integer. </summary>
    private static int _GetSeed()
    {
        var seed = new byte[sizeof(int)];

        lock (typeof(RandomEx)) {
            // Initialize the RandomNumberGenerator instance if necessary
            if (_rng == null) _rng = new RNGCryptoServiceProvider();

            // Get the random bytes
            _rng.GetBytes(seed);
        }

        // Convert the bytes to an int
        return BitConverter.ToInt32(seed, 0);
    }
}

En cours de route, j'ai également écrit et testé une implémentation qui redéfinit les méthodes nécessaires pour utiliser RNGCryptoServiceProvider afin de fournir TOUTES les valeurs aléatoires (plutôt que de compter sur le générateur de nombres aléatoires intégré à la classe System.Random). Mais je ne sais pas à quel point les résultats sont cryptographiquement puissants au moment où vous prenez mes valeurs aléatoires Sample () et que vous les transmettez à travers les transformations pour produire des valeurs entières. Quoi qu'il en soit, voici le code si quelqu'un le veut:

/// <summary> An implementation of System.Random that uses RNGCryptoServiceProvider to provide random values. </summary>
public class CryptoRandom : Random, IDisposable
{
    // Class data
    RandomNumberGenerator _csp = new RNGCryptoServiceProvider();

    /// <summary> Returns a random number between 0.0 (inclusive) and 1.0 (exclusive). </summary>
    protected override double Sample()
    {
        // Get a nonnegative random Int64
        byte[] bytes = new byte[sizeof(long)];
        _csp.GetBytes(bytes);
        long value = BitConverter.ToInt64(bytes, 0) & long.MaxValue;

        // Scale it to 0->1
        return (double)value / (((double)Int64.MaxValue) + 1025.0d);
    }

    /// <summary> Fills the elements of the specified array of bytes with random numbers. </summary>
    /// <param name="buffer"> An array of bytes to contain random numbers. </param>
    public override void NextBytes(byte[] buffer)
    {
        _csp.GetBytes(buffer);
    }

    /// <summary> Returns a nonnegative random integer. </summary>
    /// <returns> A 32-bit signed integer greater than or equal to zero. </returns>
    public override int Next()
    {
        byte[] data = new byte[4];
        _csp.GetBytes(data);
        data[3] &= 0x7f;
        return BitConverter.ToInt32(data, 0);
    }

    /// <summary> Returns a random integer that is within a specified range. </summary>
    /// <param name="minValue"> The inclusive lower bound of the random number returned. </param>
    /// <param name="maxValue"> The exclusive upper bound of the random number returned. maxValue must be greater than or equal to minValue. </param>
    /// <returns> A 32-bit signed integer greater than or equal to minValue and less than maxValue; that is, the range of return values includes minValue but not maxValue. If minValue equals maxValue, minValue is returned. </returns>
    public override int Next(int minValue, int maxValue)
    {
        // Special case
        if (minValue == maxValue) return minValue;

        double sample = Sample();
        double range = (double)maxValue - (double)minValue;
        return (int)((sample * (double)range) + (double)minValue);
    }

    #region IDisposible implementation

    /// <summary> Disposes the CryptoRandom instance and all of its allocated resources. </summary>
    public void Dispose()
    {
        // Do the actual work
        Dispose(true);

        // This object will be cleaned up by the Dispose method. Call GC.SupressFinalize to 
        // take this object off the finalization queue and prevent finalization code for this object 
        // from executing a second time.
        GC.SuppressFinalize(this);
    }

    // Dispose(bool disposing) executes in two distinct scenarios:
    //
    // If disposing is true, the method has been called directly or indirectly by a user's code and both
    // managed and unmanaged resources can be disposed. 
    //
    // If disposing is false, the method has been called by the runtime from inside the finalizer.
    // In this case, only unmanaged resources can be disposed. 
    protected virtual void Dispose(bool disposing)
    {
        if (disposing) {
            // The method has been called directly or indirectly by a user's code; dispose managed resources (if any)
            if (_csp != null) {
                _csp.Dispose();
                _csp = null;
            }

            // Dispose unmanaged resources (if any)
        }
    }

    #endregion
}
1