web-dev-qa-db-fra.com

Guid.NewGuid () VS un générateur de chaînes aléatoires de Random.Next ()

Mon collègue et moi discutons des méthodes à utiliser pour générer automatiquement des ID utilisateur et publier des ID pour identification dans la base de données:

Une option utilise une seule instance de Random et prend certains paramètres utiles afin qu'elle puisse être réutilisée pour toutes sortes de cas de génération de chaîne (c'est-à-dire de broches numériques à 4 chiffres à des identifiants alphanumériques à 20 chiffres). Voici le code:

// This is created once for the lifetime of the server instance
class RandomStringGenerator
{
    public const string ALPHANUMERIC_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    public const string ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    public const string NUMERIC = "1234567890";

    Random Rand = new Random();
    public string GetRandomString(int length, params char[] chars)
    {
        string s = "";
        for (int i = 0; i < length; i++)
            s += chars[Rand.Next() % chars.Length];

        return s;
    }
}

et l'autre option consiste simplement à utiliser:

Guid.NewGuid();

voir Guid.NewGuid sur MSDN

Nous savons tous les deux que Guid.NewGuid() fonctionnerait pour nos besoins, mais je préfère utiliser la méthode personnalisée. Il fait la même chose mais avec plus de contrôle.

Mon collègue pense que parce que la méthode personnalisée a été préparée nous-mêmes, elle est plus susceptible de générer des collisions. J'admets que je ne suis pas pleinement au courant de l'implémentation de Random, mais je suppose qu'il est tout aussi aléatoire que Guid.NewGuid (). Une utilisation typique de la méthode personnalisée peut être:

RandomStringGenerator stringGen = new RandomStringGenerator();
string id = stringGen.GetRandomString(20, RandomStringGenerator.ALPHANUMERIC_CAPS.ToCharArray());

Modifier 1:

  • Nous utilisons Azure Tables qui n'a pas de fonctionnalité d'incrémentation automatique (ou similaire) pour générer des clés.
  • Certaines réponses ici me disent simplement d'utiliser NewGuid () "car c'est pour cela qu'il est fait". Je cherche une raison plus approfondie pour expliquer pourquoi la méthode préparée peut être plus susceptible de générer des collisions avec les mêmes degrés de liberté qu'un Guid.

Édition 2:

Nous utilisions également la méthode concoctée pour générer des identifiants de publication qui, contrairement aux jetons de session, doivent être jolis pour être affichés dans l'url de notre site Web (comme http://mywebsite.com/14983336 ), les guides ne sont donc pas une option ici, mais les collisions doivent encore être évitées.

25
George Powell

Je cherche une raison plus approfondie pour expliquer pourquoi la méthode préparée peut être plus susceptible de générer des collisions avec les mêmes degrés de liberté qu'un Guid.

Tout d'abord, comme d'autres l'ont noté, Random n'est pas compatible avec les threads; son utilisation à partir de plusieurs threads peut entraîner la corruption de ses structures de données internes afin qu'il produise toujours la même séquence.

Deuxièmement, Random est initialisé en fonction de l'heure actuelle. Deux instances de Random créées dans la même milliseconde (rappelez-vous qu'une milliseconde est plusieurs million cycles de processeur sur du matériel moderne) auront la même graine, et donc produiront la même séquence .

Troisièmement, j'ai menti. Random n'est pas initialisé en fonction de l'heure actuelle; il est défini en fonction de la durée de fonctionnement de la machine. La graine est un nombre de 32 bits, et puisque la granularité est en millisecondes, cela ne prend que quelques semaines jusqu'à ce qu'elle s'enroule. Mais ce n'est pas le problème; le problème est: la période pendant laquelle vous créez cette instance de Random est très susceptible de se trouver dans quelques minutes après le démarrage de la machine. Chaque fois que vous éteignez et rallumez une machine, ou mettez une nouvelle machine en ligne dans un cluster, il y a une petite fenêtre dans laquelle des instances de Random sont créées, et plus cela se produit, plus les chances sont grandes que vous obtenez une graine que vous aviez avant.

(MISE À JOUR: les versions plus récentes du framework .NET ont atténué certains de ces problèmes; dans ces versions, vous n'avez plus tous les Random créés dans la même milliseconde ont la même graine. Cependant, il existe encore de nombreux problèmes avec Random; rappelez-vous toujours qu'il ne s'agit que d'un pseudo-aléatoire, et non d'une force cryptographique aléatoire. Random est en fait très prévisible, donc si vous comptez sur l'imprévisibilité, cela ne convient pas.)

Comme d'autres l'ont dit: si vous voulez une clé primaire pour votre base de données, alors demandez à la base de données de vous générer une clé primaire; laissez la base de données faire son travail. Si vous voulez un identifiant globalement unique, alors utilisez un guid ; c'est pour ça qu'ils sont.

Et enfin, si vous êtes intéressé à en savoir plus sur les utilisations et les abus des guides, vous voudrez peut-être lire ma série "guide guide"; la première partie est ici:

http://blogs.msdn.com/b/ericlippert/archive/2012/04/24/guid-guide-part-one.aspx

50
Eric Lippert

Comme écrit dans d'autres réponses, ma mise en œuvre a eu quelques problèmes graves:

  • Sécurité des threads: Aléatoire n'est pas sûr pour les threads.
  • Prévisibilité: la méthode n'a pas pu être utilisée pour les identifiants critiques pour la sécurité comme les jetons de session en raison de la nature de la classe Random.
  • Collisions: Même si la méthode a créé 20 nombres 'aléatoires', la probabilité d'une collision n'est pas (number of possible chars)^20 Car la valeur de départ n'est que de 31 bits et provient d'une mauvaise source. Etant donné la même graine, toute longueur de séquence sera la même.

Guid.NewGuid() serait bien, sauf que nous ne voulons pas utiliser des GUID laids dans les URL et les .NETs L'algorithme NewGuid () n'est pas connu pour être cryptographiquement sécurisé pour une utilisation dans les jetons de session - peu d'informations sont connues.

Voici le code que nous utilisons maintenant, il est sécurisé, flexible et pour autant que je sache, il est très peu probable qu'il crée des collisions s'il a suffisamment de longueur et de choix de caractères:

class RandomStringGenerator
{
    RNGCryptoServiceProvider Rand = new RNGCryptoServiceProvider();
    public string GetRandomString(int length, params char[] chars)
    {
        string s = "";
        for (int i = 0; i < length; i++)
        {
            byte[] intBytes = new byte[4];
            Rand.GetBytes(intBytes);
            uint randomInt = BitConverter.ToUInt32(intBytes, 0);
            s += chars[randomInt % chars.Length];
        }
        return s;
    }
}
7
George Powell

"Génération automatique d'ID utilisateur et publication d'ID pour identification dans la base de données" ... pourquoi ne pas utiliser une séquence ou une identité de base de données pour générer des clés?

Pour moi, votre question est vraiment: "Quelle est la meilleure façon de générer une clé primaire dans ma base de données?" Si tel est le cas, vous devez utiliser l'outil conventionnel de la base de données qui sera soit une séquence soit une identité. Ceux-ci ont des avantages sur les chaînes générées.

  1. Séquences/index d'identité mieux. Il existe de nombreux articles et articles de blog qui expliquent pourquoi les GUID et ainsi de suite font de mauvais index.
  2. Ils sont garantis d'être uniques dans le tableau
  3. Ils peuvent être générés en toute sécurité par des inserts simultanés sans collision
  4. Ils sont simples à mettre en œuvre

Je suppose que ma prochaine question est, quelles raisons envisagez-vous les GUID ou les chaînes générées? Allez-vous vous intégrer dans des bases de données distribuées? Sinon, vous devez vous demander si vous résolvez un problème qui n'existe pas.

4
Jordan Parmer

Votre méthode personnalisée présente deux problèmes:

  1. Il utilise une instance globale de Random, mais n'utilise pas de verrouillage. => L'accès multi-thread peut corrompre son état. Après quoi, la sortie sera encore plus forte qu'elle ne le fait déjà.
  2. Il utilise une graine prévisible de 31 bits. Cela a deux conséquences:
    • Vous ne pouvez pas l'utiliser pour quoi que ce soit lié à la sécurité où l'imprégnabilité est importante
    • La petite graine (31 bits) peut réduire la qualité de vos numéros. Par exemple, si vous créez plusieurs instances de Random en même temps (depuis le démarrage du système), elles créeront probablement la même séquence de nombres aléatoires.

Cela signifie que vous ne pouvez pas compter sur la sortie de Random comme étant unique, quelle que soit sa durée.

Je recommande d'utiliser un CSPRNG ( RNGCryptoServiceProvider ) même si vous n'avez pas besoin de sécurité. Ses performances sont toujours acceptables pour la plupart des utilisations, et je ferais confiance à la qualité de ses nombres aléatoires sur Random. Si vous voulez l'unicité, je vous recommande d'obtenir des nombres d'environ 128 bits.

Pour générer des chaînes aléatoires à l'aide de RNGCryptoServiceProvider, vous pouvez consulter ma réponse à Comment puis-je générer des chaînes alphanumériques aléatoires à 8 caractères en C #? .


De nos jours, les GUID renvoyés par Guid.NewGuid() sont des GUID version 4. Ils sont générés à partir d'un PRNG, ils ont donc des propriétés assez similaires à la génération d'un nombre aléatoire de 122 bits (les 6 bits restants sont fixes). Sa source d'entropie est de bien meilleure qualité que ce que Random utilise, mais sa sécurité cryptographique n'est pas garantie.

Mais l'algorithme de génération peut changer à tout moment, vous ne pouvez donc pas vous y fier. Par exemple, dans le passé, l'algorithme de génération Windows GUID) est passé de v1 (basé sur MAC + horodatage) à v4 (aléatoire).

3
CodesInChaos

Utilisation System.Guid tel qu'il:

... peut être utilisé sur tous les ordinateurs et réseaux partout où un identifiant unique est requis.

Notez que Random est un générateur de nombres pseudo-aléatoires . Ce n'est pas vraiment aléatoire, ni unique. Il n'a que 32 bits de valeur avec lesquels travailler, par rapport au GUID 128 bits.

Cependant, même les GUID peuvent avoir des collisions (bien que les chances soient vraiment minces), vous devez donc utiliser les propres fonctionnalités de la base de données pour vous donner un identifiant unique (par exemple la colonne ID d'auto-incrémentation). De plus, vous ne pouvez pas facilement transformer un GUID en un nombre numérique 4 ou 20 (alpha).

1

Contrairement à ce que certaines personnes ont dit dans le commentaire, un GUID généré par Guid.NewGuid () ne dépend d'aucun identifiant spécifique à la machine (seuls les GUID de type 1 le sont, Guid.NewGuid () renvoie un GUID de type 4, qui est principalement aléatoire).

Tant que vous n'avez pas besoin de sécurité cryptographique, la classe Random devrait être assez bonne, mais si vous voulez être plus sûr, utilisez System.Security.Cryptography.RandomNumberGenerator. Pour l'approche Guid, notez que tous les chiffres dans un GUID sont aléatoires. Citation de wikipedia :

Dans la représentation canonique, xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx, les bits les plus significatifs de N indiquent la variante (selon la variante; un, deux ou trois bits sont utilisés). La variante couverte par la spécification UUID est indiquée par les deux bits les plus significatifs de N étant 1 0 (c'est-à-dire que le N hexadécimal sera toujours 8, 9, A ou B). Dans la variante couverte par la spécification UUID, il existe cinq versions. Pour cette variante, les quatre bits de M indiquent la version UUID (c'est-à-dire que le M hexadécimal sera soit 1, 2, 3, 4 ou 5).

1
erikkallen

Concernant votre modification, voici une raison de préférer un GUID à une chaîne générée:

Le stockage natif d'un GUID (identificateur unique) dans SQL Server est de 16 octets. Pour stocker une varchar de longueur équivalente (chaîne), où chaque "chiffre" de l'ID est stocké sous forme de caractère, nécessiterait quelque part entre 32 et 38 octets, selon le formatage.

En raison de son stockage, SQL Server est également en mesure d'indexer une colonne uniqueidentifier plus efficacement qu'une colonne varchar.

0
GalacticCowboy