Je suis Charlie

Autres trucs

Accueil

Seulement les RFC

Seulement les fiches de lecture

Mon livre « Cyberstructure »

Ève

RFC 9562: Universally Unique IDentifiers (UUIDs)

Date de publication du RFC : Mai 2024
Auteur(s) du RFC : K. Davis (Cisco Systems), B. Peabody (Uncloud), P. Leach (University of Washington)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF uuidrev
Première rédaction de cet article le 12 mai 2024


Ce RFC normalise les UUID, une famille d'identificateurs uniques, obtenus sans registre central. Il remplace l'ancienne norme, le RFC 4122, avec pas mal de nouveautés et un RFC complètement refait.

Les UUID, également connus autrefois sous le nom de GUID (Globally Unique IDentifiers), sont issus à l'origine du système des Apollo, adopté ensuite dans la plate-forme DCE. Les UUID ont une taille fixe, 128 bits, et sont obtenus localement, par exemple à partir d'un autre identificateur unique comme un nom de domaine, ou bien en tirant au hasard, la grande taille de leur espace de nommage faisant que les collisions sont très improbables (section 6.7 du RFC). Ce RFC reprend la spécification (bien oubliée aujourd'hui) de DCE de l'Open Group (ex-OSF) et ajoute la définition d'un espace de noms pour des URN (sections 4 et 7). (Il existe aussi une norme ITU sur les UUID et un registre des UUID, pour ceux qui y tiennent.)

Les UUID peuvent donc convenir pour identifier une entité sur le réseau, par exemple une machine mais aussi, vu leur nombre, comme identificateur unique pour des transactions (ce qui était un de leurs usages dans DCE). En revanche, ils ne sont pas résolvables, contrairement aux noms de domaine. Mais ils sont présents dans beaucoup de logiciels (Windows, par exemple, les utilise intensivement). On les utilise comme clé primaire dans les bases de données, comme identificateur de transaction, comme nom de machine, etc.

Les UUID peuvent se représenter sous forme d'un nombre binaire de 128 bits (la section 5 du RFC décrit les différents champs qui peuvent apparaitre) ou bien sous forme texte. Sur Unix, on peut fabriquer un UUID avec la commande uuidgen, qui affiche la représentation texte standard que normalise notre RFC (section 4) :

% uuidgen 
317e8ed3-1428-4ef1-9dce-505ffbcba11a

% uuidgen
ec8638fd-c93d-4c6f-9826-f3c71436443a

Sur Linux, vous pouvez aussi simplement faire cat /proc/sys/kernel/random/uuid. Sur une machine FreeBSD, un UUID de la machine est automatiquement généré (par le script /etc/rc.d/hostid) et stocké dans le fichier /etc/hostid.

Pour l'affichage sous forme d'URN (RFC 8141), on ajoute juste l'espace uuid par exemple urn:uuid:ec8638fd-c93d-4c6f-9826-f3c71436443a. Il a été ajouté au registre IANA des espaces de noms des URN.

La section 4 du RFC détaille le format de l'UUID. En dépit des apparences, l'UUID n'est pas plat, il a une structure, mais il est très déconseillé aux applications de l'interpréter (section 6.12). Un des champs les plus importants est le champ Version (qui devrait plutôt s'appeler Type) car il existe plusieurs types d'UUID :

  • UUID version 1, fondé sur le temps (notez tout de suite que les versions 6 et 7 sont recommandées à sa place).
  • UUID version 2, réservé, non utilisé.
  • UUID version 3, fondé sur un espace de noms (par exemple le DNS) et un nom qui est condensé en MD5 (la version 5 est préférable).
  • UUID version 4, aléatoire.
  • UUID version 5, fondé sur un espace de noms et un nom qui est condensé en SHA-1.
  • UUID version 6, fondé sur le temps, avec les mêmes champs que la version 1, mais avec un format différent.
  • UUID version 7, fondé sur le temps, avec une autre définition, et des champs différents.
  • UUID version 8, pour les usages expérimentaux, si vous voulez jouer localement avec un nouveau mécanisme de génération.

Ces différents types / versions figurent dans un registre IANA. Ce registre ne peut être modifié que par une action de normalisation (cf. RFC 8126).

uuidgen, vu plus haut, peut générer des UUID de version 1 option -t, de version 3 (-m), de version 4 (c'est son comportement par défaut, mais on peut utiliser l'option -r si on veut être explicite) ou de version 5 (-s). Ici, on voit les UUID fondés sur une estampille temporelle (version 1) augmenter petit à petit :

% uuidgen -t
42ff1626-0fc7-11ef-8162-49e9505fb2f3

% uuidgen -t
4361fae8-0fc7-11ef-8162-49e9505fb2f3

% uuidgen -t
45381d02-0fc7-11ef-8162-49e9505fb2f3
  

Ici, dans le cas d'un UUID fondé sur un nom (version 3), l'UUID est stable (essayez chez vous, vous devriez obtenir le même résultat que moi), une propriété importante des UUID de version 3 et 5 :

 
% uuidgen -m -n @dns -N foobar.example
8796bf1a-793c-3c44-9ec5-a572635cd3d4

% uuidgen -m -n @dns -N foobar.example
8796bf1a-793c-3c44-9ec5-a572635cd3d4
  

Les espaces de noms sont enregistrés dans un registre IANA, d'autres peuvent être ajoutés si on écrit une spécification (cf. RFC 8126). Notez que chaque espace a son UUID (6ba7b810-9dad-11d1-80b4-00c04fd430c8 pour l'espace DNS).

Les UUID de version 6 et 7, nouveautés de ce RFC 9562, ne sont pas mis en œuvre par uuidgen, ni d'ailleurs par beaucoup d'autres programmes.

Les sections 6.1 et 6.2, elles, décrivent le processus de génération d'un UUID à base temporelle. Idéalement, il faut utiliser une graine enregistrée sur le disque (pour éviter de générer des UUID identiques) ainsi que l'instant de la génération. Mais lire sur le disque prend du temps (alors qu'on peut vouloir générer des UUID rapidement, par exemple pour identifier des transactions) et l'horloge de la machine n'a pas toujours une résolution suffisante pour éviter de lire deux fois de suite le même instant. Ces sections contiennent donc également des avis sur la génération fiable d'UUID, par exemple en gardant en mémoire le nombre d'UUID générés, pour les ajouter à l'heure.

La section 8, consacrée à la sécurité, rappelle qu'un UUID ne doit pas être utilisé comme capacité (car il est trop facile à deviner) et qu'il ne faut pas demander à un humain de comparer deux UUID (ils se ressemblent trop pour un œil humain).

Il est évidemment recommandé d'utiliser les UUID de version 5 plutôt que de version 3 (RFC 6151) mais SHA-1 a aussi ses problèmes (RFC 6194) et, de toute façon, pour la plupart des utilisations des UUID, les faiblesses cryptographiques de MD5 et de SHA-1 ne sont pas gênantes.

La section 2.1 du RFC détaille les motivations pour la mise à jour du RFC 4122 et quels sont les changements effectués. Certaines utilisations des UUID ont remis en cause des suppositions originales. Ainsi, les UUID sont souvent utilisés dans un contexte réparti, où leur capacité à être uniques sans registre central est très utile. Mais quelques points manquaient au RFC 4122 :

  • UUID version 4 (aléatoire) avait une mauvaise localité : deux UUID créés l'un après l'autre en un temps très court n'ont aucun rapport, ce qui est gênant pour certains usages (par exemple comme étiquette dans un B-tree).
  • UUID version 1 (fondé entre autre sur le temps écoulé depuis l'epoch) utilise un incrément peu pratique (cent nanosecondes).
  • Certaines méthodes fabrication des UUID posaient des gros problèmes de vie privée, par exemple l'utilisation des adresses MAC dans UUID version 1, désormais déconseillée.
  • Le RFC 4122 descendait trop dans les détails de mise en œuvre.
  • Le RFC 4122 ne séparait pas les exigences pour la génération d'UUID et celles pour leur stockage.

Seize mises en œuvre des UUID ont été étudiées pour préparer le nouveau RFC (vous avez la liste dans la section 2.1), menant aux constations suivantes :

  • Beaucoup d'errata dans le RFC 4122.
  • Spécification du format qui mélangeait trop les diverses versions d'UUID.
  • Absence de vecteurs de test (ils figurent désormais dans l'annexe A).

En Python, il existe un module UUID qui offre des fonctions de génération d'UUID de différentes versions (mais pas les plus récentes) :

import uuid

myuuid = uuid.uuid1() # Version 1, Time-based UUID
heruuid = uuid.uuid3(uuid.NAMESPACE_DNS, "foo.bar.example") # Version
            # 3, Name-based ("hash-based") UUID, a name hashed by MD5
otheruuid = uuid.uuid4() # Version 4, Random-based UUID
yetanotheruuid = uuid.uuid5(uuid.NAMESPACE_DNS,
                            "www.example.org")
                      # Version 5, a name hashed by SHA1
if (myuuid == otheruuid or \
    myuuid == heruuid or \
    myuuid == yetanotheruuid or \
    otheruuid == yetanotheruuid):
    raise Exception("They are equal, PANIC!")

print(myuuid)
print(heruuid) # Will always be the same
print(otheruuid)
print(yetanotheruuid) # Will always be the same

Et comme le dit la documentation, Note that uuid1() may compromise privacy since it creates a UUID containing the computer’s network address. (méthode de génération des UUID version 1 qui est désormais déconseillée).

Le SGBD PostgreSQL inclut un type UUID. Cela évite de stocker les UUID sous leur forme texte, ce qui est techniquement absurde et consomme 288 bits au lieu de 128 (section 6.13 du RFC).

essais=> CREATE TABLE Transactions (id uuid, value INT);
CREATE TABLE
essais=> INSERT INTO Transactions VALUES 
           ('74738ff5-5367-5958-9aee-98fffdcd1876', 42);
INSERT 0 1
essais=> INSERT INTO Transactions VALUES 
            ('88e6441b-5f5c-436b-8066-80dca8222abf', 6);
INSERT 0 1
essais=> INSERT INTO Transactions VALUES ('Pas correct', 3);
ERROR:  invalid input syntax for type uuid: "Pas correct"
LINE 1: INSERT INTO Transactions VALUES ('Pas correct', 3);
                                         ^
-- PostgreSQL peut seulement générer la version 4, les aléatoires
essais=> INSERT INTO Transactions VALUES (gen_random_uuid () , 0);
INSERT 0 1
essais=> SELECT * FROM Transactions;
                  id                  | value 
--------------------------------------+-------
 74738ff5-5367-5958-9aee-98fffdcd1876 |    42
 88e6441b-5f5c-436b-8066-80dca8222abf |     6
 41648aef-b123-496e-8a4c-52e573d17b6a |     0
(3 rows)

Attention, le RFC (section 6.13) déconseille l'utilisation des UUID fondés sur un nom pour servir de clé primaire dans la base de données, sauf si on est absolument certain (mais c'est rare) que les noms ne changeront pas.

Il existe plusieurs exemples d'utilisation des UUID. Par exemple, Linux s'en sert pour identifier les disques attachés à la machine, de préférence à l'ancien système où l'ajout d'un nouveau disque pouvait changer l'ordre des numéros sur le bus et empêcher le système de trouver un disque. Un /etc/fstab typique sur Linux contient donc des :

UUID=da8285a0-3a70-413d-baed-a1f48d7bf7b2       /home   ext3 defaults ...

plutôt que les anciens :

/dev/sda3       /home           ext3    defaults  

car sda3 n'est pas un identificateur stable. L'UUID, lui, est dans le système de fichiers et ne changera pas avec les changements sur le bus. On peut l'afficher avec dumpe2fs :


# dumpe2fs -h /dev/sda3
...
Filesystem UUID:          da8285a0-3a70-413d-baed-a1f48d7bf7b2
...

Un exemple d'utilisation de la nouvelle version 7 est décrit dans l'excellent article « Goodbye integers. Hello UUIDv7! ». Une mise en œuvre de cette version 7 apparait dans « UUIDv7 in 20 languages ».

La section 6 décrit les bonnes pratiques de génération d'un UUID. Ainsi :

  • Pour tous les UUID fondés sur le temps, si vous avez besoin d'unicité des UUID, attention aux cas où l'horloge peut changer, en raison d'une modification manuelle, d'une seconde intercalaire ou d'un ajustement fait par NTP.
  • Si vous générez des UUID de manière répartie, sur plusieurs machines, ce qui est un des points forts des UUID, mais que vous voulez quand même de l'unicité, vous pouvez préfixer vos UUID avec un identificateur de la machine, ou bien compter sur un registre central (mais, en général, c'est justement ce qu'on veut éviter avec des UUID).
  • UUID rend les collisions (génération de deux UUID identiques) peu probables mais pas impossibles. Lorsque vous concevez une application, demandez-vous si une collision est juste un petit inconvénient ou bien si elle est inacceptable (le RFC cite l'exemple fictif d'un système de contrôle aérien où chaque avion serait identifié par un UUID ; l'attribution du même UUID à deux avions différents serait clairement catastrophique). Dans ce cas, vous devez trouver un moyen de ne pas avoir de collision.
  • Si vous voulez des UUID qui ne soient pas prévisibles par un observateur extérieur, utilisez la version 4 et assurez vous que votre générateur de nombres aléatoires suit bien les prescriptions des RFC 4086 et RFC 8937, ainsi que « Random Number Generator Recommendations for Applications ».
  • Les versions 6 et 7 (nouveautés de ce RFC 9562) sont prévues pour que les UUID soient triables chronologiquement, sans avoir besoin d'analyse, uniquement en triant la version binaire. Un des avantages est que des UUID générés à des moments proches ont des valeurs proches, ce qui peut être utile dans certaines applications.

Téléchargez le RFC 9562

Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)

Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)