Choisir un identifiant : UUID, codes courts et ceux qui laissent fuiter de l'information

Quelle version d'UUID employer, pourquoi les collisions de v4 ne sont pas une inquiétude concrète, ce que trahissent les entiers séquentiels, les compromis entre alphabets pour les identifiants courts, et d'où doivent venir les bits aléatoires.

Utilisez un UUID v4 pour tout ce qui est interne, un v7 quand l'identifiant sert de clé primaire à une grande table, et un code court tiré d'un alphabet restreint quand une personne doit le lire à voix haute. Les entiers séquentiels sont l'option à éviter sur tout ce qui est public.

Les versions d'UUID qui comptent

Un UUID fait 128 bits. Quatre portent la version et deux la variante : aucune version ne dispose donc des 128 bits complets.

VersionCe qu'elle contientÀ utiliser quand
v1Horodatage sur 60 bits, séquence d'horloge sur 14 bits, identifiant de nœud sur 48 bits (l'adresse MAC)Uniquement pour des données héritées
v3 / v5Un hachage MD5 ou SHA-1 d'un espace de noms et d'un nomIl faut des identifiants déterministes
v4122 bits aléatoires, rien d'autreChoix par défaut, quand rien ne doit pouvoir se déduire
v7Horodatage en millisecondes sur 48 bits, puis 74 bits aléatoiresDes clés où l'ordre d'insertion compte

La v1 est celle dont il faut se méfier. Elle intègre l'heure de création et, dans la plupart des implémentations, l'adresse MAC de la machine qui l'a produite, ce qui a permis à de tels identifiants de remonter jusqu'à l'auteur du virus Melissa en 1999. La v7, normalisée par la RFC 9562 en 2024, publie délibérément l'heure de création : c'est à la fois sa fonctionnalité et son coût.

La question des collisions

Un UUID v4 compte 122 bits aléatoires : il en existe donc 2^122, soit environ 5,3 x 10^36. Ce qui compte, c'est la borne des anniversaires, pas la taille de l'espace : la probabilité que deux valeurs parmi n coïncident vaut à peu près n au carré divisé par 2^123. Mille milliards d'UUID générés donnent environ une chance sur dix mille milliards d'obtenir un doublon, et il faut environ 2,7 x 10^18 valeurs pour atteindre la probabilité d'un pile ou face, soit quelque 85 ans à raison d'un milliard par seconde.

« Pas une préoccupation pratique » ne veut toujours pas dire « impossible », et l'écart ne tient jamais à l'arithmétique. Les doublons viennent du générateur : une graine figée, une machine virtuelle clonée avec son réservoir d'entropie, une image de conteneur livrée avec un état de PRNG enregistré. L'unicité tient à la source d'aléa, pas au format : gardez donc la contrainte d'unicité sur la colonne.

Ordre, localité d'index et fuites

Les identifiants aléatoires se dispersent. Dans un index B-tree, chaque insertion tombe sur une page feuille au hasard : l'ensemble de travail devient l'index entier plutôt que son bord droit, les taux de succès du cache chutent et les pages se scindent. Une clé qui croît avec le temps s'ajoute toujours au même endroit et garde les mêmes quelques pages en cache. C'est la raison d'être de la v7 et d'ULID ; un ULID, ce sont 48 bits d'horodatage en millisecondes plus 80 bits aléatoires, écrits sur 26 caractères triables en base32 de Crockford.

Le prix à payer est la prévisibilité : un identifiant ordonné dans le temps annonce quand son enregistrement a été créé, si bien qu'une poignée d'entre eux révèle les cadences d'inscription et les heures creuses. Si cela pose problème, prenez la v4 et acceptez le coût sur l'index.

Les entiers séquentiels vont plus loin. /invoices/1041 indique que vous avez émis un millier de factures environ, et deux identifiants espacés d'une semaine donnent votre taux de croissance. Pire, un attaquant peut demander 1040. Si le serveur renvoie cet enregistrement au motif qu'il existe et que l'appelant est connecté, sans vérifier à qui il appartient, il s'agit d'un défaut de contrôle d'accès, répertorié sous le nom de référence directe non sécurisée à un objet. Un identifiant impossible à deviner ne remplace pas un contrôle d'autorisation, mais il empêche bel et bien de parcourir la table.

Les slugs sont eux aussi des identifiants, et ne laissent rien fuiter sur les volumes lorsqu'ils sont construits correctement : accents réduits, casse abaissée, ponctuation ramenée à des traits d'union, comme le fait le Générateur de slug. Gardez-en un stable après publication ; le modifier casse les liens entrants.

Identifiants courts, alphabets et longueur

Chaque caractère d'un code aléatoire vaut le log2 de la taille de l'alphabet : 5,95 bits pour le base62, 5 pour le base32, 4 pour l'hexadécimal. La longueur et l'alphabet fixent la probabilité de collision pour un volume donné.

Longueur (base62)Valeurs distinctesBitsRisque de collision sur 1 milliard d'identifiants
82,2 x 10^1443,7Certaine
108,4 x 10^1759,5Environ 45 %
123,2 x 10^2171,5Environ 1 sur 6 500
164,8 x 10^2895,3Environ 1 sur 100 milliards

Cela suppose que chaque caractère est aléatoire de façon indépendante ; un préfixe comme inv_ n'ajoute aucun bit.

Dès qu'un humain doit le lire, le base62 est le mauvais alphabet. 0 et O, 1, l et I sont des erreurs de transcription en puissance, et un code sensible à la casse ne survit pas à un appel téléphonique. Le base32 de Crockford élimine I, L, O et U et accepte les deux casses en entrée, ce qui vaut bien un caractère de plus.

D'où viennent les bits aléatoires

Math.random() n'est pas une source cryptographique. V8 l'implémente avec xorshift128+, dont l'état interne se retrouve à partir d'une courte série de sorties, après quoi chaque valeur future devient prévisible. Cela convient pour mélanger une démonstration ou pour un texte de remplissage. Cela ne convient pas pour un identifiant de session, une clé d'API, un lien de réinitialisation ou un jeton.

// Browsers, Node 19+, Deno and Bun
const id = crypto.randomUUID();

const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);

Ailleurs : crypto.randomBytes en Node, secrets.token_urlsafe en Python, crypto/rand en Go, SecureRandom en Java, random_bytes en PHP. Le Générateur d'UUID produit des valeurs v4 cryptographiquement aléatoires et le Générateur de mots de passe s'appuie sur la même API du navigateur ; Nombres aléatoires sert à l'échantillonnage, pas aux secrets.

Un piège quand on écrit le sien : projeter un octet aléatoire sur un alphabet de 62 caractères avec % 62 favorise les huit premiers caractères ; recourez à l'échantillonnage par rejet.

Textes de remplissage et données d'exemple

Le lorem ipsum est du Cicéron malmené, et il perdure parce qu'il est illisible : un texte qui ressemble à une langue sans en être une permet de juger une mise en page sur sa forme et sur la longueur de ses lignes plutôt que sur ses phrases, et c'est exactement ce que produit le générateur Lorem Ipsum. Remplacez-le par un contenu réaliste avant validation, car le latin de remplissage masque le débordement que provoquera un vrai titre.

Les données inventées obéissent à une seule règle : on ne doit jamais pouvoir les confondre avec les vraies.

  • Les numéros de carte proviennent de la plage de test publiée par un prestataire de paiement, ou échouent franchement au contrôle de Luhn. Un nombre de 16 chiffres tiré au hasard qui passe le contrôle de Luhn appartient à quelqu'un.
  • Les identifiants nationaux utilisent des plages réservées. Les numéros de sécurité sociale américains commençant par 000, 666 ou 900 à 999 ne sont jamais attribués.
  • Servez-vous de example.com et de ses équivalents, réservés par la RFC 2606, ainsi que du bloc de documentation 192.0.2.0/24.

Les données de test atteignent la production plus souvent qu'on ne le croit : un script d'amorçage pointé sur la mauvaise base, un texte de remplissage oublié dans un modèle d'e-mail envoyé à toute la liste. Les faux enregistrements doivent avoir l'air manifestement faux, pour qu'un humain en repère un avant que le support n'agisse dessus.

Les QR codes en bref

Un QR code est une grille de modules, des versions 1 à 40, de 21x21 jusqu'à 177x177. La longueur de la charge utile détermine la version, et à taille d'impression fixe, une version plus élevée signifie des modules plus petits, donc une meilleure impression et une lecture de plus près. Raccourcissez l'URL avant de la donner au Générateur de QR code, et notez que le mode alphanumérique n'accepte que les majuscules : HTTPS://EXAMPLE.COM s'encode donc plus petit que sa forme en minuscules.

La correction d'erreurs compte quatre niveaux, qui récupèrent environ 7 % (L), 15 % (M), 25 % (Q) et 30 % (H) d'un code abîmé. La redondance coûte de la capacité : un niveau plus élevé pousse donc la même charge utile vers une version plus dense. M est le choix par défaut habituel ; retenez Q ou H pour une impression en petit format, une surface courbe, ou un logo posé au milieu, ce qui fonctionne justement parce que la redondance l'absorbe.

Deux exigences physiques décident si un code se lit ou non. La zone de silence est une marge vide de quatre modules sur chaque côté, dont le décodeur se sert pour repérer la limite : un code collé à une bordure ou à une photographie échoue donc souvent. Le contraste doit être franchement sombre sur franchement clair : un bleu pâle sur blanc est un échec courant, et beaucoup de décodeurs refusent les codes inversés.