Elegir un identificador: UUID, códigos cortos y los que filtran información

Qué versión de UUID usar, por qué las colisiones de v4 no son una preocupación práctica, qué revelan los enteros secuenciales, qué compensaciones tienen los alfabetos de los códigos cortos y de dónde deben salir los bits aleatorios.

Usa un UUID v4 para cualquier cosa interna, un v7 cuando el identificador sea la clave primaria de una tabla grande y un código corto de alfabeto restringido cuando una persona tenga que leerlo en voz alta. Los enteros secuenciales son la opción que hay que evitar en todo lo que sea público.

Las versiones de UUID que importan

Un UUID son 128 bits. Cuatro llevan la versión y dos la variante, así que ninguna versión dispone de los 128 completos.

VersiónQué contieneÚsala cuando
v1Marca de tiempo de 60 bits, secuencia de reloj de 14 bits, ID de nodo de 48 bits (la dirección MAC)Solo para datos heredados
v3 / v5Un hash MD5 o SHA-1 de un espacio de nombres y un nombreHagan falta identificadores deterministas
v4122 bits aleatorios y nada másPor defecto, cuando no se deba poder inferir nada
v7Marca de tiempo en milisegundos de 48 bits y después 74 bits aleatoriosSean claves en las que importa el orden de inserción

v1 es la versión que sirve de advertencia. Incrusta la hora de creación y, en la mayoría de implementaciones, la dirección MAC de la máquina que lo generó, que es como esos identificadores ayudaron a rastrear al autor del virus Melissa en 1999. v7, estandarizado en el RFC 9562 en 2024, publica la hora de creación a propósito: es su virtud y su coste.

La cuestión de las colisiones

Un UUID v4 tiene 122 bits aleatorios, así que existen 2^122, unos 5,3 x 10^36. Lo que importa es la cota del cumpleaños, no el tamaño del espacio: la probabilidad de que dos de n valores cualesquiera coincidan es de aproximadamente n al cuadrado dividido entre 2^123. Un billón de UUID generados suponen alrededor de una probabilidad entre diez billones de que haya un duplicado, y para llegar a la probabilidad de una moneda al aire hacen falta unos 2,7 x 10^18 valores, unos 85 años a mil millones por segundo.

«No es una preocupación práctica» sigue sin ser «imposible», y la diferencia nunca está en la aritmética. Los duplicados vienen del generador: una semilla fija, una máquina virtual clonada junto con su reserva de entropía, una imagen de contenedor que se distribuye con el estado del PRNG guardado. La unicidad es cosa de la fuente aleatoria, no del formato, así que mantén la restricción de unicidad en la columna.

Orden, localidad del índice y filtraciones

Los identificadores aleatorios se dispersan. En un índice B-tree cada inserción cae en una página hoja al azar, así que el conjunto de trabajo pasa a ser el índice entero en vez de su extremo derecho, las tasas de acierto de la caché bajan y las páginas se dividen. Una clave que crece con el tiempo se añade siempre en el mismo sitio y mantiene calientes esas mismas pocas páginas. Para eso existen v7 y ULID; un ULID son 48 bits de marca de tiempo en milisegundos más 80 bits aleatorios, escritos como 26 caracteres ordenables de base32 de Crockford.

El coste es la previsibilidad: un identificador ordenado por tiempo publica cuándo se creó su registro, así que un puñado de ellos revela tasas de alta y horas tranquilas. Si eso importa, usa v4 y paga el coste en el índice.

Los enteros secuenciales van más lejos. /invoices/1041 dice que has emitido alrededor de mil facturas, y dos identificadores separados por una semana dan tu tasa de crecimiento. Peor aún, un atacante puede pedir el 1040. Si el servidor devuelve ese registro porque existe y quien llama ha iniciado sesión, sin comprobar de quién es, eso es un fallo de control de acceso, catalogado como referencia directa insegura a objetos. Un identificador imposible de adivinar no es una comprobación de autorización, pero sí impide que alguien recorra la tabla entera.

Los slugs también son identificadores y no filtran nada sobre el volumen si se construyen bien: acentos plegados, todo en minúsculas y la puntuación reducida a guiones, que es lo que hace el generador de slugs. Mantén uno estable después de publicarlo; cambiarlo rompe los enlaces entrantes.

Códigos cortos, alfabetos y longitud

Cada carácter de un código aleatorio vale el log2 del tamaño del alfabeto: 5,95 bits para base62, 5 para base32 y 4 para hexadecimal. La longitud y el alfabeto fijan la probabilidad de colisión para un volumen dado.

Longitud (base62)Valores distintosBitsProbabilidad de colisión en mil millones de identificadores
82,2 x 10^1443,7Segura
108,4 x 10^1759,5Alrededor del 45%
123,2 x 10^2171,5Alrededor de 1 entre 6.500
164,8 x 10^2895,3Alrededor de 1 entre cien mil millones

Eso da por hecho que cada carácter es aleatorio de forma independiente; un prefijo como inv_ no añade ningún bit.

Para cualquier cosa que vaya a leer una persona, base62 es el alfabeto equivocado. 0 y O, o 1, l e I, son errores de transcripción esperando a ocurrir, y un código que distingue mayúsculas de minúsculas no sobrevive a una llamada de teléfono. La base32 de Crockford elimina I, L, O y U y acepta cualquiera de las dos cajas en la entrada, lo que compensa el carácter de más.

De dónde salen los bits aleatorios

Math.random() no es una fuente criptográfica. V8 la implementa con xorshift128+, cuyo estado interno se puede recuperar a partir de una serie corta de salidas, y a partir de ahí todos los valores futuros son predecibles. Vale para barajar en una demo o para un valor de relleno. No vale para un identificador de sesión, una clave de API, un enlace de restablecimiento o un token.

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

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

En otros entornos: crypto.randomBytes en Node, secrets.token_urlsafe en Python, crypto/rand en Go, SecureRandom en Java y random_bytes en PHP. El generador de UUID produce valores v4 criptográficamente aleatorios y el generador de contraseñas usa la misma API del navegador; el generador de números aleatorios está pensado para muestreo, no para secretos.

Una trampa si te lo montas por tu cuenta: proyectar un byte aleatorio sobre un alfabeto de 62 caracteres con % 62 sesga el resultado hacia los ocho primeros caracteres; usa muestreo por rechazo.

Datos de relleno y de ejemplo

El lorem ipsum es Cicerón destrozado, y sobrevive precisamente porque no se puede leer: un texto que parece lenguaje sin serlo permite juzgar una maquetación por su forma y la longitud de sus líneas en vez de por sus frases, que es lo que produce el generador de lorem ipsum. Sustitúyelo por textos realistas antes de dar el visto bueno, porque el latín de relleno esconde el desbordamiento que provocará un titular de verdad.

Los datos inventados necesitan una sola regla: nunca se deben poder confundir con los de verdad.

  • Los números de tarjeta salen del rango de pruebas publicado por un proveedor de pagos, o directamente no pasan la comprobación de Luhn. Un número aleatorio de 16 dígitos que pase Luhn es de alguien.
  • Los identificadores nacionales usan rangos reservados. Los números de la Seguridad Social estadounidense que empiezan por 000, 666 o del 900 al 999 no se emiten nunca.
  • Usa example.com y sus hermanos, reservados por el RFC 2606, y el bloque de documentación 192.0.2.0/24.

Los datos de prueba llegan a producción más a menudo de lo que se espera: un script de carga apuntando a la base de datos equivocada, un valor de relleno olvidado en una plantilla de correo que se envía a toda la lista. Los registros falsos deben parecer falsos a simple vista, para que una persona detecte uno antes de que soporte actúe sobre él.

Los códigos QR en resumen

Un código QR es una cuadrícula de módulos, con versiones de la 1 a la 40, de 21x21 hasta 177x177. La longitud de la carga marca la versión, y con un tamaño de impresión fijo una versión más alta significa módulos más pequeños, que exigen mejor impresión y un escaneo más cercano. Acorta la URL antes de pasársela al generador de códigos QR, y ten en cuenta que el modo alfanumérico solo admite mayúsculas, así que HTTPS://EXAMPLE.COM se codifica más pequeño que la forma en minúsculas.

La corrección de errores tiene cuatro niveles, que recuperan aproximadamente el 7% (L), el 15% (M), el 25% (Q) y el 30% (H) de un código dañado. La redundancia cuesta capacidad, así que un nivel más alto empuja la misma carga a una versión más densa. M es el valor por defecto habitual; elige Q o H para impresiones pequeñas, superficies curvas o un logotipo en el centro, que funciona porque la redundancia lo absorbe.

Hay dos requisitos físicos que deciden si un código se puede escanear. La zona de silencio es un margen despejado de cuatro módulos por cada lado, que el decodificador usa para encontrar el borde, así que un código pegado a un marco o a una fotografía falla a menudo. El contraste tiene que ser oscuro de verdad sobre claro de verdad: el azul pálido sobre blanco es un fallo habitual, y muchos decodificadores rechazan los códigos invertidos.