Cómo se generan los UUID



Probablemente ya haya utilizado UUID en sus proyectos y pensó que eran únicos. Echemos un vistazo a los aspectos principales de la implementación y veamos por qué los UUID son prácticamente únicos, ya que existe una pequeña posibilidad de que ocurran los mismos valores.



La implementación moderna de UUID se remonta a RFC 4122, que describe cinco enfoques diferentes para generar estos identificadores. Repasaremos cada uno de ellos y veremos la implementación de la versión 1 y la versión 4.



Teoría



UUID (identificador único universal) es un número de 128 bits que se utiliza en el desarrollo de software como un identificador único para los elementos. Su representación textual clásica es una serie de 32 caracteres hexadecimales, separados por guiones en cinco grupos en el patrón 8-4-4-4-12.



Por ejemplo:



3422b448-2460-4fd2-9183-8000de6f8343


La información de implementación de UUID está incrustada en esta secuencia de caracteres aparentemente aleatoria:



xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx


Los valores en las posiciones M y N definen la versión y variante del UUID, respectivamente.



Versión



El número de versión se identifica por los cuatro bits más significativos en la posición M. Hoy existen las siguientes versiones:





Opción



Este campo define la plantilla de la información incluida en el UUID. La interpretación de todos los demás bits del UUID depende del valor de la variante.



Lo determinamos por los primeros 1-3 bits más significativos en la posición N.





Hoy en día, la opción 1 se usa con mayor frecuencia, en la que MSB0es igual 1y MSB1es igual 0. Esto significa que dado comodines - bits seleccionados - únicos valores posibles son 8, 9, Ao B.



Nota:



1 0 0 0 = 8

1 0 0 1 = 9

1 0 1 0 = A

1 0 1 1 = B


Entonces, si ve un UUID con tales valores en la posición N, entonces este es el identificador en la opción 1.



Versión 1 (tiempo + id de host único o aleatorio)



En este caso, el UUID se genera así: se agrega alguna propiedad de identificación del dispositivo que genera el UUID a la hora actual, la mayoría de las veces es la dirección MAC (también conocida como ID de nodo).



El identificador se obtiene concatenando una dirección MAC de 48 bits, una marca de tiempo de 60 bits, una secuencia de reloj "única" de 14 bits y 6 bits reservados para los UUID de versión y variante.



La secuencia del reloj es simplemente un valor que se incrementa cada vez que se cambia el reloj.


La marca de tiempo utilizada en esta versión es el número de intervalos de 100 nanosegundos desde el 15 de octubre de 1582, fecha en que se originó el calendario gregoriano.



Puede que estés familiarizado con el sistema de tiempo Unix desde el comienzo de una época. Es solo un tipo diferente de Día Cero. Hay servicios en la web que te ayudarán a transformar una representación temporal en otra, así que no nos detengamos ahí.


Aunque esta implementación parece bastante simple y confiable, el uso de la dirección MAC de la máquina en la que se genera el identificador no permite que este método se considere universal. Especialmente cuando la seguridad es el criterio principal. Por lo tanto, en algunas implementaciones, en lugar del identificador de nodo, se utilizan 6 bytes aleatorios tomados de un generador de números aleatorios protegido criptográficamente.



La construcción de la versión 1 de UUID es la siguiente:



  1. Se toman los 32 bits menos significativos de la marca de tiempo UTC actual. Estos serán los primeros 4 bytes (8 caracteres hexadecimales) del UUID [ TimeLow].
  2. Se toman los 16 bits centrales de la marca de tiempo UTC actual. Estos serán los siguientes 2 bytes (4 caracteres hexadecimales) [ TimeMid].
  3. Los siguientes 2 bytes (4 caracteres hexadecimales) concatenan los 4 bits de la versión UUID con los 12 MSB restantes de la marca de tiempo UTC actual (que tiene un total de 60 bits) [ TimeHighAndVersion].
  4. Los siguientes 1-3 bits definen la variante de la versión UUID. Los bits restantes contienen una secuencia de reloj que agrega un poco de aleatoriedad a esta implementación. Esto evita colisiones cuando varios generadores UUID se ejecutan en el mismo sistema: o el reloj del sistema se retrasa para el generador o el cambio de hora se ralentiza [ ClockSequenceHiAndRes && ClockSequenceLow].
  5. Los últimos 6 bytes (12 caracteres hexadecimales, 48 ​​bits) son el "ID de nodo", que suele ser la dirección MAC del generador [ NodeID].


El UUID de la versión 1 se genera mediante concatenación:



TimeLow + TimeMid + TimeHighAndVersion + (ClockSequenceHiAndRes && ClockSequenceLow) + NodeID 


Dado que esta implementación depende del reloj, necesitamos manejar situaciones de borde. Primero, para minimizar la correlación entre sistemas, de manera predeterminada, la secuencia de reloj se toma como un número aleatorio; esto se hace solo una vez durante todo el ciclo de vida del sistema. Esto nos brinda el beneficio adicional de admitir ID de nodo que se pueden transmitir a través de sistemas, ya que la secuencia de reloj inicial es completamente independiente de la ID de nodo.



Recuerde que el propósito principal de usar una secuencia de reloj es agregar algo de aleatoriedad a nuestra ecuación. Los bits de secuencia de reloj ayudan a extender la marca de tiempo y se adaptan a situaciones en las que se generan varios UUID incluso antes de que cambie el reloj del procesador. De esta manera evitamos crear los mismos identificadores cuando el reloj se retrasa (el dispositivo está apagado) o el identificador de nodo cambia. Si el reloj está retrasado, o podría haberse retrasado (por ejemplo, mientras el sistema estaba apagado), y el generador de UUID no puede verificar que los identificadores se generen con marcas de tiempo posteriores al valor de reloj especificado, entonces se debe cambiar la secuencia del reloj. Si conocemos su valor anterior, simplemente podemos aumentarlo;de lo contrario, debe configurarse aleatoriamente o con un PRNG de alta calidad.



Versión 2 (seguridad de un entorno informático distribuido)



La principal diferencia de esta versión con la anterior es que en lugar de "aleatoriedad" en la forma de los bits menos significativos de la secuencia de reloj, aquí se utiliza un identificador característico del sistema. A menudo, esta es solo la identificación del usuario actual. La versión 2 se usa con menos frecuencia, difiere muy poco de la versión 1, así que sigamos adelante.



Versión 3 (nombre + hash MD5)



Si se necesitan identificadores únicos para la nomenclatura o la información de nomenclatura, generalmente se usa UUID versión 3 o versión 5.



Estos codifican cualquier entidad "nombrada" (sitios, DNS, texto sin formato, etc.) en un valor UUID. Lo más importante es que se generará el mismo UUID para el mismo espacio de nombres o texto.



Tenga en cuenta que el espacio de nombres en sí es un UUID.



let namespace = “digitalbunker.dev”
let namespaceUUID = UUID3(.DNS, namespace)

// Ex: 
UUID3(namespaceUUID, “/category/things-you-should-know-1/”) 
4896c91b-9e61-3129-87b6-8aa299028058

UUID3(namespaceUUID, “/category/things-you-should-know-2/”) 
29be0ee3-fe77-331e-a1bf-9494ec18c0ba

UUID3(namespaceUUID, “/category/things-you-should-know-3/”) 
33b06619-1ee7-3db5-827d-0dc85df1f759


En esta implementación, el espacio de nombres de UUID se convierte en una cadena de bytes concatenados con el nombre de entrada, luego hash con MD5, lo que da como resultado 128 bits para el UUID. Luego reescribimos algunos de los bits para reproducir con precisión la versión y la información de la versión, y dejamos el resto intacto.



Es importante comprender que ni el espacio de nombres ni el nombre de entrada se pueden calcular en función del UUID. Esta es una operación irreversible. La única excepción es la fuerza bruta cuando el atacante ya conoce uno de los valores (espacio de nombres o texto).



Con la misma entrada, los UUID generados de la versión 3 y 5 serán deterministas.



Versión 4 (PRNG)



Implementación más sencilla.



6 bits están reservados para versión y variante, todavía quedan 122 bits. Esta versión simplemente genera 128 bits aleatorios y luego reemplaza 6 de ellos con la versión y los datos de la versión.



Dichos UUID dependen completamente de la calidad del PRNG (generador de números pseudoaleatorios). Si su algoritmo es demasiado simple o carece de valores iniciales, aumenta la probabilidad de que se repitan los identificadores.



En los lenguajes modernos, se usa con mayor frecuencia la versión UUID 4.



Su implementación es bastante simple:



  1. Generamos 128 bits aleatorios.
  2. Vuelva a escribir algunos bits con la versión correcta y la información de la versión:



    1. Tome el séptimo bit y 0x0FY para borrar el mordisco alto. Y luego 0x40OR se usa para asignar la versión 4.
    2. Luego tomamos el noveno byte, realizamos la 0x3Foperación AND en cy la 0x80operación OR en él.
  3. Convierta 128 bits a hexadecimal e inserte guiones.


Versión 5 (nombre + SHA-1-hash)



La única diferencia con la versión 3 es que estamos usando el algoritmo hash SHA-1 en lugar de MD5. Esta versión se prefiere a la tercera (SHA-1> MD5).



Práctica



Una de las ventajas importantes de los UUID es que su singularidad no depende de una autoridad central de autorización o de la coordinación entre diferentes sistemas. Cualquiera puede crear un UUID con cierta confianza en que nadie más generará este valor en el futuro previsible.



Esto hace posible combinar identificadores creados por diferentes participantes en una base de datos, o mover identificadores entre bases de datos con una probabilidad de colisión insignificante.



Los UUID se pueden utilizar como claves primarias en bases de datos, como nombres únicos para archivos cargados, como nombres únicos para cualquier fuente web. No necesita una autoridad de autorización central para generarlos. Pero esta es una solución de doble filo. Debido a la falta de un controlador, es imposible rastrear los UUID generados.



Hay algunos inconvenientes más que deben abordarse. La aleatoriedad inherente aumenta la seguridad, pero dificulta la depuración. Además, el UUID puede ser redundante en algunas situaciones. Digamos que no tiene sentido usar 128 bits para identificar de forma única los datos cuyo tamaño total es inferior a 128 bits.



Unicidad



Puede parecer que si tiene suficiente tiempo, puede repetir algún valor. Especialmente en el caso de la versión 4. Pero en realidad este no es el caso. Si tuviera que generar mil millones de UUID por segundo durante 100 años, entonces la probabilidad de que uno de los valores se repita sería de aproximadamente el 50%. Esto se debe al hecho de que el PRNG proporciona una cantidad suficiente de entropía (verdadera aleatoriedad), de lo contrario, la probabilidad de un doble será mayor. Un ejemplo más ilustrativo: si generó 10 billones de UUID, la probabilidad de que aparezcan dos valores idénticos es del 0,00000006%.



Y en el caso de la versión 1, el reloj se pondrá a cero solo en 3603. Entonces, si no planea mantener su servicio en funcionamiento hasta 1583, entonces está a salvo.



Sin embargo, la probabilidad de aparición de un doble se mantiene, y en algunos sistemas intentan tenerlo en cuenta. Pero en la gran mayoría de los casos, los UUID pueden considerarse completamente únicos. Si necesita más pruebas, aquí hay una visualización simple de la probabilidad de colisión en la práctica.



All Articles