3 points par GN⁺ 2024-08-27 | 1 commentaires | Partager sur WhatsApp
  • Les UUID comptent 8 versions, de v1 à v8 ; un numéro plus élevé ne signifie pas qu’une version est plus récente ou meilleure, mais que différents modes de génération sont définis dans la RFC 9562
  • Dans la pratique, le choix se réduit généralement à v4 et v7 : v4 est le choix par défaut pour des identifiants aléatoires, tandis que v7 convient lorsqu’un tri par ordre de génération est nécessaire
  • v1 et v6 utilisent les mêmes ingrédients, mais v6 réorganise les champs de façon à obtenir un ordre chronologique de génération lors du tri
  • v3 et v5 créent des UUID en hachant des données d’entrée ; v3 utilise MD5, v5 utilise SHA-1, avec DNS et URL comme valeurs d’entrée possibles
  • En pratique, il est pertinent d’utiliser v7 plutôt que v1 ou v6 si possible ; v5 si vous avez besoin d’un UUID basé sur des données d’entrée ; et v8 si vous avez besoin d’un UUID entièrement personnalisé

Modes de génération selon les versions d’UUID

  • Les versions d’UUID vont de v1 à v8 et sont toutes définies dans la RFC 9562
  • UUID basés sur le temps

    • UUID Version 1 est généré à partir d’un horodatage, d’un compteur monotone et d’une adresse MAC
    • UUID Version 6 utilise les mêmes données que v1, mais en change l’ordre afin qu’un tri corresponde à l’ordre chronologique de génération
    • UUID Version 7 est généré à partir d’un horodatage et de données aléatoires
  • UUID aléatoires et personnalisés

    • UUID Version 4 est généré entièrement à partir de données aléatoires et correspond à peu près à la forme à laquelle beaucoup pensent quand on parle d’UUID
    • UUID Version 8 est entièrement personnalisable, à l’exception des champs version/variant nécessaires à toutes les versions
  • UUID basés sur le hachage de données d’entrée

    • UUID Version 3 est généré à partir du hachage MD5 des données fournies par l’utilisateur
      • Les valeurs d’entrée candidates dans la RFC incluent DNS et URL
    • UUID Version 5 est généré à partir du hachage SHA-1 des données fournies par l’utilisateur
      • Comme pour v3, DNS et URL peuvent être des entrées candidates
  • UUID réservés

    • UUID Version 2 est réservé aux ID de sécurité et ne comporte pas de détails connus

Critères de choix en pratique

  • Dans la plupart des cas, le choix se fait entre v4 ou v7
  • Si vous avez simplement besoin d’un identifiant aléatoire, v4 est un bon choix par défaut
  • Si l’ID doit pouvoir être trié, v7 peut être envisagé
    • Par exemple, v7 peut être un candidat lorsqu’un UUID est utilisé comme clé de base de données
  • v5 ou v8 sont plutôt utilisés lorsque l’on veut intégrer ses propres données dans l’UUID
    • Pour ce type d’usage, l’utilisateur sait généralement déjà qu’il en a besoin
  • Selon la RFC, v7 améliore v1 et v6 ; il faut donc utiliser v7 plutôt que v1 ou v6 si possible
    • Si v1 ou v6 est absolument nécessaire, v6 peut être utilisé
  • v2 est réservé à des usages de sécurité non spécifiés
  • v3 a été remplacé par v5, qui utilise un hachage plus fort, et les cas où v3 est nécessaire sont probablement déjà connus de l’utilisateur

1 commentaires

 
GN⁺ 2024-08-27
Avis de Hacker News
  • Dire qu’il n’y a pas de détails sur UUID v2, c’est vrai seulement si l’on s’en tient à la lecture du RFC notoirement obscur : https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#t...
    On n’en rencontre presque jamais, mais il existe aussi des UUID version 0. Ils méritent d’être mentionnés, car ils sont à l’origine des bits réservés qui ont ensuite permis de définir d’autres « versions » de façon compatible. J’ai résumé les recherches associées dans ma bibliothèque UUID : https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSA...
    J’ai décidé de les prendre en charge parce que je trouve ça élégant, mais il me reste encore à trancher la gestion du rollover de date et la façon de traiter les UID Apollo plus anciens.

    • Pour être juste, le RFC9562 cite deux documents de spécification des UUID Version 2. En revanche, le RFC4122 était un peu trop cryptique à mon goût.
      Je commence à travailler sur un RFC informatif qui aiderait les gens à comprendre les types d’UUID historiques définis dans l’espace Variant 0-7. Si vous voulez participer à la discussion ou relire la formulation, voir https://github.com/yocto/draft-yocto-uuid.
  • Les détails se trouvaient en deux minutes. Il suffisait de cliquer sur le lien de l’article vers la section du RFC 9562 qui définit DCE, puis sur le premier lien de ce paragraphe vers la spécification, et enfin de chercher « UUID » pour arriver à l’annexe A : tout y est.
    Le nom, « Universal Unique Identifier », prête à confusion, mais le contenu nécessaire est bien là. Ce serait bien de cliquer au moins sur les liens que l’on met dans son propre article.

    • Il y a deux choses à apprendre ici. L’une consiste à lire la spécification des UUID v2, l’autre à déduire, à partir d’un UUID, des informations sur le contexte dans lequel il a été généré.
      Dans le contexte, comme les autres versions sont décrites comme étant générées à partir de certains éléments précis, la phrase parle clairement du second point. Elle est un peu ambiguë, mais difficile de dire qu’elle induit vraiment en erreur.
    • J’ai suivi exactement le même chemin, et comme l’article semblait ne pas trop se soucier des détails, je ne l’ai pas lu jusqu’au bout.
      Cela dit, l’annexe était intéressante à lire, comme un instantané de l’époque.
    • C’est peut-être un article écrit par un modèle de langage.
  • J’aimerais bien qu’il existe une norme d’UUID courts du genre 73WakrfVbNJBaAmhQtEeDv ou bK7nP9xM.
    Strictement parlant, ce ne seraient sans doute pas des UUID, puisqu’ils pourraient entrer en collision quelque part, mais je voudrais un ensemble standard d’ID aléatoires tout en restant assez courts pour être mémorisables.

    • Ce qui s’en rapproche le plus, à mon avis, c’est ULID. C’est du base32 sur 26 caractères, donc court, sur 128 bits, et triable lexicographiquement.
      S’il n’existe pas de norme plus populaire, c’est probablement parce qu’il faut renoncer à quelque chose. 128 bits donnent un risque de collision faible dans presque tous les usages, mais plus on réduit la taille, plus il faut raisonner sur le contexte concret et les conséquences d’une collision, ce qui rend la standardisation difficile. Avec d’autres encodages comme base64 ou base85, on peut faire plus court, mais on sacrifie des aspects comme la sensibilité à la casse ou la compatibilité sûre avec les URL : https://github.com/ulid/spec
    • Si vous avez besoin d’un UUID plus compact, vous pouvez représenter un UUID existant en base64 sûr pour les URL, sous une forme de 22 caractères.
      Ce n’est qu’une autre représentation du même UUID, réversible. Un UUID étant au final une valeur de 128 bits, il s’agit davantage d’une notation alternative que d’une vraie transformation.
    • Sqids pourrait correspondre. Cela produit des ID bien plus courts que des UUID, mais ils ne sont pas universellement uniques et sont générés à partir de séquences d’entiers : https://sqids.org/
    • En général, on génère N bits aléatoires et on les encode en base58. N se choisit selon le besoin.
      On perd l’avantage de tri monotone présent dans certaines versions d’UUID, mais base58 est sûr pour les URL et ne contient pas de caractères spéciaux. Les valeurs peuvent toujours être stockées en binaire. Par exemple, dans Postgres, on peut utiliser bytea au lieu d’une colonne texte.
    • Un travail est en cours pour standardiser des techniques d’encodage alternatives permettant de représenter des UUID 128 bits sous une forme textuelle plus courte.
      La discussion est ici : https://github.com/uuid6/new-uuid-encoding-techniques-ietf-d...
  • Le timestamp des UUID v7 a beaucoup changé les choses pour Databend. Nous l’utilisons pour retrouver rapidement les fichiers de métadonnées par timestamp dans AWS S3, ce qui accélère nettement des opérations comme vacuum.
    PR : https://github.com/datafuselabs/databend/pull/16049

    • C’est intéressant, mais utiliser des UUID pour chercher par temps ne me viendrait pas vraiment à l’esprit. Personnellement, je mettrais plutôt un champ timestamp séparé.
      Le grand avantage des UUID triés chronologiquement est leur meilleure localité. Quand on ajoute une nouvelle entrée à un index, cela revient le plus souvent à l’ajouter à la fin, ce qui peut coûter moins cher que des insertions aléatoires. Cela dit, cela peut aussi augmenter la contention ; on peut donc envisager un hybride avec quelques bits aléatoires avant le timestamp pour créer des « shards » triés. Les lectures portent aussi souvent sur les données récentes, donc avoir ces données regroupées et bien présentes en cache est utile.
    • C’est aussi très bien pour les stockages clé-valeur comme DynamoDB. C’est beaucoup plus propre que des clés composites préfixées par un timestamp ou une date ISO, et préférable au gaspillage d’un index secondaire pour les timestamps.
  • Il est difficile de comprendre l’objectif de uuid2. Je ne savais même pas qu’il existait davantage de types de ce genre, et j’ai vu uuid2 pour la première fois en demandant à Xandr de supprimer mes données personnelles : https://news.ycombinator.com/item?id=40913915
    Même en lisant Wikipédia, je ne comprends pas bien pourquoi, après avoir créé quelque chose appelé « identifiant unique universel », on a prévu plusieurs types, dont certains ont été conçus à l’origine pour permettre de remonter jusqu’au PC. Je me demande si mélanger une partie du code MAC rend uuid2 plus proche de l’aléatoire, ou s’il y a une autre raison. Du point de vue de la protection des données personnelles, je me demande aussi pourquoi on ne pourrait pas simplement utiliser de longs identifiants avec un très grand nombre de caractères possibles, afin de rendre la probabilité de collision pratiquement nulle.

    • L’objectif initial était d’identifier les messages dans l’architecture de calcul distribué Apollo. Les UID, puis les UUID, étaient une manière réversible de marquer l’intersection de deux dimensions.
      N’importe quelles deux machines pouvaient produire le même UID/UUID pour les mêmes deux entrées, et le destinataire du message identifié pouvait reconstituer les composants d’origine à partir de l’identifiant. Comme ils étaient conçus comme des étiquettes pour des messages temporaires, les deux dimensions étaient le temps et l’ID matériel ; au début, il s’agissait du numéro de série Apollo, puis plus tard de l’adresse matérielle Ethernet, entre autres.
      Je pense qu’une grande partie de la confusion vient du fait que, dans les premières implémentations d’AEGIS, les ingénieurs d’Apollo ont commencé à utiliser des UID « canned », c’est-à-dire statiques et bien connus, pour l’identification dans le système de fichiers. Avec le temps, l’usage courant des UUID s’est complètement déplacé : d’identifiants temporaires où les doublons étaient intentionnels, ils sont devenus des identifiants canned où il faut éviter les collisions ; et les deux dimensions sont devenues l’aléatoire et encore l’aléatoire.
      L’histoire est plus complexe. Microsoft a recruté l’une des personnes clés d’Apollo pour créer MSRPC pour Windows NT, ce qui a aussi donné naissance aux GUID. Les GUID ont une disposition des champs différente de celle des UUID et, contrairement à ce qu’affirment de nombreuses sources, ils ne sont pas en endianité mixte. Microsoft utilise volontiers des GUID canned non seulement pour identifier les messages RPC temporaires, mais aussi pour à peu près tout ce qui nécessite des identifiants bien connus : classes COM, codecs média, etc. Exemple : https://gix.github.io/media-types/
      Désolé de lier deux fois mon dépôt dans le même fil de commentaires, mais j’ai commencé à résumer cette histoire dans le README de ma bibliothèque UUID, et il faudrait que je m’y remette. Apollo a démarré en 1980, tandis que le brouillon de la RFC UUID de Leach/Salz n’est arrivé qu’en 1998 ; il manque donc énormément de choses dans les standards modernes : https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSA...
  • UUID v4 n’est rien d’autre qu’un générateur d’octets aléatoires avec des tirets placés à des positions définies. Il n’est pas indispensable de l’utiliser : on peut générer soi-même des octets aléatoires et économiser de l’espace.
    Cela réduit aussi les tirets inutiles, les informations de version, etc.

    • Un UUID est un nombre de 128 bits, et la représentation sous forme de chaîne avec des tirets n’est qu’une des nombreuses façons de représenter ce nombre.
      C’est un peu comme une adresse IPv4, qui est un nombre de 32 bits, tandis que la notation « quatre groupes séparés par des points » n’est qu’une de ses représentations. Si vous pensez à un UUID comme à un format de chaîne, vous vous trompez déjà sur le concept le plus fondamental des UUID. Même si l’on veut seulement un identifiant aléatoire, je trouve élégant qu’un UUID aléatoire contienne quelques bits de drapeau indiquant « ceci est censé être aléatoire ». C’est utile lorsqu’on tombe sur un identifiant isolé, sans contexte.
    • UUID v4 fixe aussi 4 bits à une valeur donnée pour indiquer qu’il s’agit de la version 4.
      On peut débattre de l’utilité de créer des espaces de noms différents entre plusieurs méthodes de génération, mais un générateur aléatoire ordinaire n’a qu’une chance sur 16 de produire un UUIDv4 valide. Bien sûr, si vous voulez écrire votre propre générateur d’UUID, régler correctement les bits est trivial.
    • C’est vrai, mais pour la plupart des développeurs, l’attrait principal est la simplicité d’implémentation. Presque tous les langages disposent d’une bibliothèque UUID qui fonctionne en une ligne.
      En Go, uuid.New().String() suffit, alors que lire des données aléatoires avec crypto/rand puis les convertir en base64 ou en hexadécimal demande plus de lignes et d’efforts.
  • Je déconseille d’utiliser les versions basées sur MAC. En théorie, cela peut concerner toutes les versions sauf v4 et v7, mais v1 est la pire.
    v3 a aussi le problème que MD5 est gravement cassé.

    • MD5 est « cassé » en tant que fonction de hachage cryptographique. Comme fonction de hachage non cryptographique, il reste parfaitement acceptable.
  • Je ne connaissais pas les détails au-delà de la version 4, mais ce qui manque vraiment et serait utile serait probablement une méthode utilisant des données SHA256 et un compteur, un peu dans l’esprit de PBKDF2.
    Cela pourrait servir d’identifiant dérivé préservant la confidentialité, et permettre aussi de prouver de façon souple qu’un UUID donné a été dérivé d’une certaine graine.

    • Si vous en avez vraiment besoin, vous pouvez voir UUIDv4 comme un format d’encodage, produire une sortie de 122 bits avec un algorithme cryptographique, puis l’encoder en UUID.
      Sinon, vous finirez par vouloir une sortie plus longue.
    • C’est similaire à v3, mais sous une forme qui permet de choisir l’algorithme de hachage.
  • Il suffit d’utiliser v7.
    C’est maintenant au tour des experts sécurité de dire non.

    • Si la date de génération peut être une information sensible, ou si l’UUID doit être totalement imprévisible, utilisez v4. Sinon, utilisez v7.
    • Ce qui m’a toujours frustré avec les UUID et les ULID, c’est qu’à ma connaissance, il n’existe pas de bonne méthode pour les générer de façon déterministe.
      Dans beaucoup de cas d’usage, il serait très utile de pouvoir retraiter les données et obtenir le même ID, mais je ne connais pas de manière standard d’y parvenir.
    • Je ne comprends pas pourquoi on balaie aussi légèrement les questions liées à la sécurité.