Est-il acceptable de compter sur des intrants aléatoires uniques?

42

Je suis en train d'implémenter un protocole réseau et j'ai besoin que les paquets possèdent des identifiants uniques. Jusqu'à présent, je viens de générer des entiers aléatoires sur 32 bits, en supposant qu'il est astronomiquement peu probable qu'il y ait une collision pendant la durée de vie d'un programme / d'une connexion. Est-ce généralement considéré comme une pratique acceptable dans le code de production ou faut-il concevoir un système plus complexe pour éviter les collisions?

Phénix
la source
47
Pourquoi l'utilisation d'un entier séquentiel ne va-t-elle pas le couper?
Whatsisname
20
Pourquoi n'utilisez-vous pas un int incrémentant? Les GUID , conçus pour avoir les propriétés d'unicité que vous décrivez, ont une taille de 128 bits et non de 32.
Robert Harvey
21
Vous pouvez également attribuer un numéro de canal à chaque ordinateur connecté et utiliser un identifiant de séquence incrémenté. Les deux numéros combinés (le numéro de canal reprenant les bits de poids fort) deviennent votre nouvel identifiant unique.
Robert Harvey
27
Si votre "générateur de nombres aléatoires" garantit qu'un nombre particulier ne sera pas répété tant qu'un autre nombre n'a pas été généré, il s'agit d'un très mauvais générateur de nombres aléatoires! Dans la même logique, la seule séquence "aléatoire" de tirages au sort serait HTHTHTHT ....
alephzero
17
"J'ai besoin que les paquets aient des identifiants uniques" Quelle est la conséquence de la violation de cette exigence? Si vous avez besoin d' identifiants uniques, dans la lecture la plus stricte du mot, vous devez disposer d'un identifiant centralisé du système (par exemple, comment les MAC sont affectés aux sociétés de cartes réseau individuelles). Très probablement, vous avez une définition plus souple de "exiger". Comprendre ce niveau de douceur va changer radicalement les réponses que vous recevez.
Cort Ammon

Réponses:

142

Attention au paradoxe de l' anniversaire .

Supposons que vous générez une séquence de valeurs aléatoires (uniformément, indépendamment) à partir d'un ensemble de taille N (N = 2 ^ 32 dans votre cas).

Ensuite, la règle empirique pour le paradoxe de l'anniversaire stipule qu'une fois que vous avez généré environ des valeurs sqrt (N), il y a au moins 50% de chances qu'une collision se soit produite, c'est-à-dire qu'il existe au moins deux valeurs identiques dans le champ. séquence générée.

Pour N = 2 ^ 32, sqrt (N) = 2 ^ 16 = 65536. Ainsi, après avoir généré environ 65k identifiants, il est plus probable que deux d’entre eux se heurtent! Si vous générez un identifiant par seconde, cela se produirait en moins d'un jour. Il va sans dire que de nombreux protocoles de réseau fonctionnent bien plus rapidement que cela.

nomadictype
la source
11
+1 Lors de mon dernier emploi, l'un de nos partenaires avait en fait utilisé cette approche pour générer des identifiants aléatoires (non pas pour les paquets réseau, mais pour un objet métier partagé créé en dernier lieu par les clients finaux). Quand j'ai interrogé les données avec un œil sur cela, j'ai trouvé qu'en moyenne, il y avait deux à trois paires de doublons chaque jour. (Heureusement, cela ne casse les choses que si les doublons ont été créés à moins de quatre heures les uns des autres, ce qui est arrivé un peu moins souvent. Mais toujours.)
ruakh
6
(cliquez ici pour rendre les maths) Pour ce que ça vaut, l'approximation $ \ sqrt {N} $ est précise jusqu'à un facteur constant; pour $ N = 2 ^ {32} $, le seuil réel est 77164, car il s'agit de la plus petite valeur de $ n $ telle que $ \ prod_ {k = 1} ^ {n-1} (1 - k / N) <1 / 2. $
wchargin
4
@wchargin: Il n'y a vraiment rien de magique à propos de la probabilité d'atteindre 0,5; ce qui est remarquable, c’est que la probabilité augmente relativement rapidement avec N. Si les identifiants 32 bits avaient une chance légère mais non négligeable de collision aléatoire, un identifiant 40 bits n’en aurait presque aucun.
Supercat
3
@supercat: Tout cela est vrai. Je pensais juste que si on fournissait une telle constante, on pourrait aussi bien donner une valeur précise :-)
wchargin
2
@wchargin: Je préfère penser aux endroits où il faut commencer à se préoccuper des doublons. Si l'on se situe bien en dessous de sqrt (N), les probabilités de collisions diminuent rapidement, au point que l'on peut affirmer sans risque qu'elles ne se produiront que si le générateur aléatoire présente un défaut grave.
Supercat
12

Il est largement considéré comme acceptable de s’appuyer sur des nombres aléatoires uniques si ces nombres ont suffisamment de bits. Il existe des protocoles cryptographiques dans lesquels la répétition d'un nombre aléatoire annule toute la sécurité. Et tant que le générateur de nombres aléatoires utilisé ne contient pas de vulnérabilités sérieuses, cela ne pose pas de problème.

L'un des algorithmes de génération d'identificateurs UUID générera efficacement un identifiant composé de 122 bits aléatoires et supposera qu'il sera unique. Et deux des autres algorithmes reposent sur le fait qu'une valeur de hachage tronquée à 122 bits est unique, ce qui présente à peu près le même risque de collision.

Il existe donc des normes qui supposent que 122 bits suffisent pour créer un identifiant aléatoire unique, mais 32 bits ne suffisent certainement pas. Avec les identifiants 32 bits, il ne faut qu'environ 2¹⁶ ID avant que le risque de collision n'atteigne 50%, car avec 2¹⁶ ID, il y aura près de 2³¹ paires, chacune pouvant être une collision.

Même 122 bits est inférieur à ce que je recommanderais dans tout nouveau design. Si le respect de certaines normes est important pour vous, utilisez des UUID. Sinon, utilisez quelque chose de plus grand que 122 bits.

La fonction de hachage SHA1 avec une sortie de 160 bits n'est plus considérée comme sécurisée, en partie parce que 160 bits ne suffisent pas pour garantir l'unicité des sorties. Les fonctions de hachage modernes ont des sorties de 224 à 512 bits. Les identifiants générés aléatoirement doivent viser les mêmes tailles pour garantir l'unicité avec une marge de sécurité suffisante.

Kasperd
la source
12
SHA-1 est considéré comme peu sûr car il existe des attaques spécifiques (c'est-à-dire non aléatoires) contre l'algorithme lui-même qui peuvent trouver des collisions plus rapidement que la force brute, et non parce qu'il y a un risque élevé de collision aléatoire. Selon une estimation approximative, avec 122 bits et un taux de génération de 1 milliard (10 ^ 9) ID par seconde, il faudrait plus de 73 ans pour atteindre une chance de collision de 50%.
8bittree
sqrt(2^122)= 2,3 quadrillions de quadrillions d'UUID
nozɐɹƆ
2
@ 8bittree Le réseau bitcoin calcule 2 has hashes SHA2 toutes les 10 minutes. Si cela avait été SHA1 hachage, il ne faudrait qu'une semaine pour produire une collision. Si les UUID étaient générés à la même vitesse que bitcoin calcule les hachages, il faudrait moins de 2 secondes pour produire une collision.
Kasperd
Bitcoin consiste essentiellement à essayer de trouver des collisions. Il est extrêmement populaire et dispose d’un matériel dédié spécialement conçu pour la recherche de hachages. Maintenant, bien sûr, si le PO envisage de créer une crypto-monnaie très populaire, ou quelque chose de similaire, il peut avoir besoin de centaines ou de milliers de bits par ID. Mais en supposant immédiatement que ce soient les exigences, cela pourrait encourager beaucoup plus de travail que nécessaire si une bibliothèque UUID standard est suffisante.
8bittree
@ 8bittree Si l'utilisation de bibliothèques standard est un avantage, optez pour l'UUID. Mais extraire quelques octets aléatoires urandomn'est pas plus fastidieux que d'utiliser une bibliothèque UUID. Je viens d'implémenter les deux en Python à des fins de comparaison, et chaque méthode comportait exactement 25 caractères de code source.
Kasperd
3

J'appellerais cela une mauvaise pratique. Les nombres aléatoires générés ne créent simplement pas des nombres uniques, ils créent simplement des nombres aléatoires. Une distribution aléatoire est susceptible d'inclure quelques doublons. Vous pouvez rendre cette circonstance assez improbable en ajoutant un élément de temps. Si vous obtenez l'heure actuelle de l'horloge système en millisecondes. Quelque chose comme ça:

parseToInt(toString(System.currentTimeMillis()) + toString(Random.makeInt()))

Ira un long chemin. De toute évidence, pour garantir réellement l'unicité, vous devez utiliser UUID / GUID. Mais ils peuvent être coûteux à générer, ce qui précède est probablement suffisant, car la seule possibilité de chevauchement est que si le générateur aléatoire ait un doublon dans la même milliseconde.

Fresheyeball
la source
9
1ms peut être long dans certains systèmes.
quant_dev
7
Cela ne diminue en rien le risque de collision. La probabilité d'une collision après N nombres est exactement égale à celle de la solution d'origine du PO. L'astuce consistant à utiliser l'heure actuelle comme une graine est généralement utilisée lors de l'attribution séquentielle de clés.
Cort Ammon
2
@Fresheyeball Je suis convaincu que cela n'a aucun effet, à moins que Random.makeInt () ne génère pas une distribution uniforme de la valeur minimale de l'entier à la valeur maximale de l'entier. Pour chaque valeur passée générée par cette fonction, il existe une valeur aléatoire issue de makeInt qui, pour ce pas de temps exact, génère cette valeur, ce qui crée une collision. Puisque toutes les valeurs de makeInt sont équiprobables, la probabilité de collision est exactement égale à celle de la probabilité de collision sans addition de temps.
Cort Ammon
2
@CortAmmon, cela n'utilise pas l'heure actuelle comme une graine , et cela fait vraiment une différence tant que ces N nombres n'ont pas tous été générés pendant la même milliseconde, car deux nombres avec des parties d'horodatage différentes ne se rencontrent jamais . Si vous imaginez l'exemple d'un autre paquet présentant un risque de collision de 50% en moins d'un jour par seconde, celui-ci a 0% de chance de collision pour un paquet par seconde, au moins jusqu'au moment currentTimeMillisopportun.
Hobbs
3
@ Hobbs Vous oubliez le débordement d'entier. Maintenant, si la clé utilisée par l'OP était une structure contenant 2 entiers, un contenant System.currentTimeMilliset l'autre contenant Random.makeInt(), alors la probabilité d'une collision diminue considérablement. Cependant, ce n'est pas ce que le code dans cet exemple fait. Quelle que soit l'heure et la valeur aléatoire précédentes, et quelle que soit l'heure actuelle, la probabilité de collision est identique à la probabilité que deux nombres aléatoires entrent en collision.
Cort Ammon
3

Cela dépend à la fois de la probabilité d'échec et des conséquences d'un échec.

Je me souviens d'un débat opposant des personnes travaillant sur les logiciels et le matériel. Les personnes travaillant sur le matériel considéraient qu'un algorithme avec une faible probabilité de résultats erronés (environ 1 défaillance sur 100 ans) était acceptable et que les personnes travaillant sur les logiciels pensaient que c'était un anathème. Il s’est avéré que les spécialistes du matériel calculaient régulièrement les taux de défaillance attendus et étaient très habitués à l’idée que tout donnerait de temps en temps des réponses erronées, par exemple en raison de perturbations causées par les rayons cosmiques; ils ont trouvé étrange que les logiciels s’attendent à une fiabilité à 100%.

Michael Kay
la source
1

Bien sûr, vous avez de très faibles probabilités que deux entiers aléatoires de 32 bits soient séquentiels, mais ce n'est pas complètement impossible. La décision technique appropriée est basée sur les conséquences des collisions, une estimation du volume de nombres que vous générez, la durée de vie pour laquelle l'unicité est requise et ce qui se passe si un utilisateur malveillant tente de provoquer des collisions.

Sean McSomething
la source
0

Il peut être acceptable de supposer que les nombres aléatoires seront uniques, mais vous devez faire attention.

En supposant que vos nombres aléatoires soient distribués de manière égale, la probabilité de collision est approximativement (n 2/2 ) / k, où n est le nombre de nombres aléatoires que vous générez et k le nombre de valeurs possibles pouvant être prises par un nombre "aléatoire".

Vous ne mettez pas un chiffre astronomiquement improbable, alors prenons le chiffre 1 sur 2 30 (environ 1 milliard). Disons en outre que vous générez 2 à 30 paquets (si chaque paquet représente environ un kilo-octet de données, cela signifie environ un téraoctet de données totales, mais sans que cela ne soit pas imaginable). Nous constatons que nous avons besoin d'un nombre aléatoire avec au moins 2 89 valeurs possibles.

Premièrement, vos nombres aléatoires doivent être suffisamment grands. Un nombre aléatoire de 32 bits peut avoir au plus 2 32 valeurs possibles. Pour un serveur occupé qui est loin d'être assez élevé.

Deuxièmement, votre générateur de nombres aléatoires doit avoir un état interne suffisamment grand. Si votre générateur de nombres aléatoires a uniquement un état interne à 32 bits, quelle que soit la taille de la valeur que vous en générez, vous n'obtiendrez toujours que 2 32 valeurs possibles au maximum.

Troisièmement, si vous souhaitez que les nombres aléatoires soient uniques dans toutes les connexions plutôt que dans une seule connexion, votre générateur de nombres aléatoires doit être bien amorcé. Cela est particulièrement vrai si votre programme est redémarré fréquemment.

En général, les générateurs de nombres aléatoires "normaux" dans les langages de programmation ne conviennent pas à une telle utilisation. Les générateurs de nombres aléatoires fournis par les bibliothèques de cryptographie sont généralement.

Peter Green
la source
0

Certaines des réponses ci-dessus reposent sur l'hypothèse que le générateur de nombres aléatoires est bien «plat», c'est-à-dire que la probabilité que deux nombres soient générés est la même.

Ce n'est probablement pas vrai pour la plupart des générateurs de nombres aléatoires. La plupart d'entre eux utilisent un polynôme d'ordre élevé appliqué de manière répétée à une graine.

Cela dit, de nombreux systèmes dépendent de ce schéma, généralement avec des identificateurs UUID. Par exemple, chaque objet et élément de Second Life a un UUID 128 bits, généré de manière aléatoire, et ils se rencontrent rarement.

Anniepoo
la source
0

Beaucoup de gens ont déjà donné des réponses de grande qualité, mais je voudrais ajouter quelques points mineurs: tout d’abord, le point de @nomadictype sur le paradoxe de l’anniversaire est excellent .

Un autre point: le caractère aléatoire n’est pas aussi simple à générer et à définir que l’on pourrait supposer. (En fait, il existe des tests statistiques pour le caractère aléatoire disponibles).

Cela dit, il est important de connaître l’ erreur du joueur , qui est une erreur statistique selon laquelle les gens présument que des événements indépendants s’influencent mutuellement. Les événements aléatoires sont généralement statistiquement indépendants les uns des autres. Par exemple, si vous générez un "10" de manière aléatoire, cela ne change en rien votre probabilité future de générer plus de "10". (Peut-être que quelqu'un pourrait proposer une exception à cette règle, mais je m'attendrais à ce que ce soit le cas pour à peu près tous les générateurs de nombres aléatoires).

Donc, ma réponse est que si vous pouviez supposer qu'une séquence suffisamment longue de nombres aléatoires était unique, il ne s'agirait pas vraiment de nombres aléatoires, car ce serait un modèle statistique clair. En outre, cela impliquerait que chaque nouveau nombre ne soit pas un événement indépendant, car si vous générez, par exemple, un 10, cela signifierait que la probabilité de générer des 10 à venir serait de 0% (cela ne pourrait probablement pas arriver), plus cela signifierait que vous augmenteriez les chances d'obtenir un nombre autre que 10 (c'est-à-dire que plus vous générez de nombres, plus la probabilité de chacun des nombres restants augmente).

Une dernière chose à considérer: la chance de gagner le Powerball en jouant un seul jeu est, si je comprends bien, d’environ 1 sur 175 millions. Cependant, les chances de quelqu'un gagner sont considérablement plus élevés que cela. Vous êtes plus intéressé par les chances de quelqu'un « gagnant » (c. -à- Etre un double) que dans les chances de tout nombre particulier « gagnant » / étant un doublon.

EJoshuaS - Rétablir Monica
la source
Si l’on génère des identifiants de 4096 bits de telle sorte que chaque bit ait la même probabilité d’être égal à 0 ou 1 indépendamment de tout autre bit généré dans le même identifiant ou dans un autre identifiant, la probabilité que deux identificateurs soient identiques être extrêmement petit même si l’on devait générer de manière aléatoire un identifiant différent pour chacun des atomes à peu près 4.0E81 de l’univers observable. Le fait que de tels identifiants soient presque certainement uniques ne les rendrait en aucun cas "non aléatoires"
supercat
@supercat C'est vrai - compte tenu d'un nombre suffisamment important, il est très peu probable qu'il y ait des doublons, mais ce n'est pas impossible. Cela dépend vraiment de la gravité des conséquences de la non-unicité si la description du PO est une bonne idée.
EJoshuaS - Réintégrer Monica
Si la probabilité d'une collision aléatoire est inférieure à la probabilité qu'un météore efface les périphériques qui s'appuient sur des identifiants uniques, du point de vue de l'ingénierie, inutile de vous soucier de l'ancien. Il y aurait un grand besoin de s'inquiéter de tout ce qui pourrait empêcher les nombres aléatoires de ne pas être indépendants, mais les collisions aléatoires ne seraient pas un problème.
Supercat
@supercat Je pense que vous avez mal interprété cela. Voir l'autre réponse sur le paradoxe de l'anniversaire. Je pense qu'une collision est beaucoup plus probable que vous ne le calculez: le PO utilise simplement un nombre 32 bits, alors je ne sais pas trop où vous allez. 4096, et comme nomadictype a montré, la probabilité d’une collision éventuelle avec un nombre de cette longueur est en fait étonnamment élevée.
EJoshuaS
Vous avez raison de dire qu'un nombre 32 bits est trop court, même pour de petites populations, si les collisions sont totalement inacceptables. Si vous utilisez un nombre suffisamment grand, vous pouvez réduire la probabilité de collisions aléatoires au point où vous pouvez supposer en toute sécurité qu'ils ne se produiront pas, et dans de nombreux cas, utiliser un nombre plus grand peut être préférable à une autre méthode de calcul. assurer l'unicité, car cette dernière nécessite généralement de pouvoir accéder à des transitions d'état impossibles à annuler ou à annuler, même si l'horloge du système est réinitialisée ou si le système est rechargé à partir d'une sauvegarde.
Supercat
0

Peu importe le nombre de bits que vous utilisez - vous ne pouvez PAS garantir que deux nombres "aléatoires" seront différents. Au lieu de cela, je vous suggère d'utiliser quelque chose comme l'adresse IP ou une autre adresse réseau de l'ordinateur et un numéro séquentiel, de préférence un nombre séquentiel HONKIN 'BIG - 128 bits (évidemment non signé) sonne comme un bon début, mais 256 serait meilleur.

Bob Jarvis
la source
-1

Non bien sûr que non. Sauf si vous utilisez des échantillons sans remplacement, les chances de duplication sont minimes.

Dr. Drew
la source