Generando UUID v5. ¿Qué es el nombre y el espacio de nombres?

125

He leído la manpágina, pero no lo hago undestand namey namespaceson para.

Para los UUID de la versión 3 y la versión 5, se debe proporcionar el nombre y el espacio de nombres de los argumentos de la línea de comandos adicionales. El espacio de nombres es un UUID en representación de cadena o un identificador para los UUID de espacio de nombres predefinidos internamente (actualmente conocidos son "ns: DNS", "ns: URL", "ns: OID" y "ns: X500"). El nombre es una cadena de longitud arbitraria.

El espacio de nombres:

El espacio de nombres es un UUID en representación de cadena o un

¿Significa que necesito almacenarlo (UUID v4) en algún lugar en relación con el UUID v5 generado? En cualquier caso, ¿por qué no se hace automáticamente?

El nombre es una cadena de longitud arbitraria.

nameuna cadena completamente aleatoria? Entonces, ¿cuál es el propósito de esto? ¿Se puede decodificar desde el UUID v5?

Gajus
fuente

Respuestas:

106

El nombre y el espacio de nombres se pueden usar para crear una jerarquía de (muy probablemente) UUID únicos.

En términos generales, un UUID de tipo 3 o de tipo 5 se genera al combinar un identificador de espacio de nombres con un nombre. Los UUID de tipo 3 usan MD5 y los UUID de tipo 5 usan SHA1. Solo están disponibles 128 bits y se usan 5 bits para especificar el tipo, por lo que todos los bits hash no se incluyen en el UUID. (También MD5 se considera criptográficamente roto, y SHA1 está en sus últimas etapas, así que no use esto para verificar datos que deben ser "muy seguros"). Dicho esto, le brinda una forma de crear una función de "hash" repetible / verificable que mapee un nombre posiblemente jerárquico en un valor probabilísticamente único de 128 bits, actuando potencialmente como un hash jerárquico o MAC.

Suponga que tiene una tienda (clave, valor), pero solo admite un espacio de nombres. Puede generar una gran cantidad de espacios de nombres lógicos distintos utilizando UUID de tipo 3 o tipo 5. Primero, cree un UUID raíz para cada espacio de nombres. Esto podría ser un UUID de tipo 1 (host + marca de tiempo) o de tipo 4 (aleatorio) siempre que lo guarde en algún lugar. Alternativamente, puede crear un UUID aleatorio para su raíz (o usar el nullUUID: 00000000-0000-0000-0000-000000000000como raíz) y luego crear un UUID reproducible para cada espacio de nombres usando " uuid -v5 $ROOTUUID $NAMESPACENAME". Ahora puede crear UUID únicos para claves dentro de un espacio de nombres usando "uuid -v5 $NAMESPACEUUID $KEY". Estos UUID se pueden colocar en un único almacén de valores-clave con una alta probabilidad de evitar colisiones. Este proceso se puede repetir de forma recursiva de modo que si, por ejemplo, el" valor "asociado con una clave UUID representa a su vez algún tipo de" espacio de nombres lógico " "como un depósito, contenedor o directorio, su UUID se puede utilizar a su vez para generar UUID más jerárquicos.

El UUID de tipo 3 o tipo 5 generado contiene un hash (parcial) de la identificación del espacio de nombres y el nombre dentro del espacio de nombres (clave). No contiene el UUID del espacio de nombres más de lo que un mensaje MAC contiene el contenido del mensaje desde el que está codificado. El nombre es una cadena "arbitraria" (octeto) desde la perspectiva del algoritmo uuid. Sin embargo, su significado depende de su aplicación. Podría ser un nombre de archivo dentro de un directorio lógico, un ID de objeto dentro de un almacén de objetos, etcétera.

Si bien esto funciona bien para una cantidad moderadamente grande de espacios de nombres y claves, eventualmente se agota si está apuntando a una gran cantidad de claves que son únicas con una probabilidad muy alta. La entrada de Wikipedia para el problema del cumpleaños (también conocida como la paradoja del cumpleaños) incluye una tabla que da las probabilidades de al menos una colisión para varios números de teclas y tamaños de tabla. Para 128 bits, el hash de 26 mil millones de claves de esta manera tiene una probabilidad de colisión de p=10^-18(insignificante), pero 26 billones de claves, aumenta la probabilidad de al menos una colisión con p=10^-12(uno en un billón), y el hash de 26*10^15claves aumenta la probabilidad de al menos una colisión parap=10^-6(uno en un millón). Ajustándose a 5 bits que codifican el tipo UUID, se agotará un poco más rápido, por lo que un billón de claves tienen aproximadamente una probabilidad de 1 en un billón de tener una sola colisión.

Consulte http://en.wikipedia.org/wiki/Birthday_problem#Probability_table para ver la tabla de probabilidad.

Consulte http://www.ietf.org/rfc/rfc4122.txt para obtener más detalles sobre las codificaciones UUID.

Jeff Anderson-Lee
fuente
2
En un cierto nivel en la jerarquía, ¿puedo usar un UUIDv5 como espacio de nombres y UUIDv4 como clave aleatoria para garantizar que las colisiones en los datos en sí (que están siendo identificados por este GUID) no aumenten las posibilidades de colisionar los UUID? ¿Algún problema de rendimiento que deba conocer?
ermik
Soy nuevo en el concepto y estaba desconcertado de cuál es esa jerarquía de la que estás hablando. ¿Dónde puedo verlo, etc.? Una vez que me atasqué en la explicación, llegó algo de claridad, esto podría usarse para crear un UUID reproducible para el espacio de nombres . Me pregunto si hay alguna manera de verificar que un UUID determinado (de tipo 3 o 5) se haya generado utilizando un espacio de nombres en particular (su UUID).
msciwoj
213

Los UUID de tipo 3 y tipo 5 son solo una técnica para rellenar un hash en un UUID.

  • Tipo 1: rellena la dirección MAC + fecha y hora en 128 bits
  • Tipo 3 : mete un hash MD5 en 128 bits
  • Tipo 4: almacena datos aleatorios en 128 bits
  • Tipo 5 : rellena un hash SHA1 en 128 bits
  • Tipo 6: idea no oficial para UUID secuenciales

Un hash SHA1 genera 160 bits (20 bytes); el resultado del hash se convierte en un UUID.

Con el hash de 20 bytes de SHA1:

SHA1 Digest:   74738ff5 5367 e958 9aee 98fffdcd1876 94028007
UUID (v5):     74738ff5-5367-5958-9aee-98fffdcd1876
                             ^_low nibble is set to 5, to indicate type 5
                                  ^_first two bits set to 1 and 0, respectively

(Tenga en cuenta que los dos primeros bits de '9' ya son 1 y 0, respectivamente, por lo que esto no tiene ningún efecto).

¿Qué hago hash?

Probablemente se esté preguntando qué es lo que se supone que debo hacer. Básicamente, hash la concatenación de:

sha1([NamespaceUUID]+[AnyString]);

Prefija su cadena con un espacio de nombres para evitar conflictos de nombres.

El UUID RFC define previamente cuatro espacios de nombres para usted:

  • NameSpace_DNS: {6ba7b810-9dad-11d1-80b4-00c04fd430c8}
  • NameSpace_URL: {6ba7b811-9dad-11d1-80b4-00c04fd430c8}
  • NameSpace_OID: {6ba7b812-9dad-11d1-80b4-00c04fd430c8}
  • NameSpace_X500: {6ba7b814-9dad-11d1-80b4-00c04fd430c8}

Entonces, podrían hash juntos:

StackOverflowDnsUUID = sha1(Namespace_DNS + "stackoverflow.com");
StackOverflowUrlUUID = sha1(Namespace_URL + "stackoverflow.com");

El RFC luego define cómo:

  • tomar los 160 bits de SHA1
  • y convertirlo en 128 bits de un UUID

La esencia básica es tomar solo los primeros 128 bits, rellenar a 5en el registro de tipo y luego establecer los dos primeros bits de la clock_seq_hi_and_reservedsección en 1 y 0, respectivamente.

Más ejemplos

Ahora que tiene una función que genera un llamado Nombre , puede tener la función (en pseudocódigo):

UUID NameToUUID(UUID NamespaceUUID, String Name)
{
    byte[] hash = sha1(NamespaceUUID.ToBytes() + Name.ToBytes());
    UUID result;
    Copy(hash, result, 16);
    result[6] &= 0x0F; 
    result[6] |= 0x50;
    result[8] &= 0x3F; 
    result[8] |= 0x80;
    return result;
}

(Tenga en cuenta que la endianidad de su sistema puede afectar los índices de los bytes anteriores)

Puedes tener llamadas:

uuid = NameToUUID(Namespace_DNS, 'www.stackoverflow.com');
uuid = NameToUUID(Namespace_DNS, 'www.google.com');
uuid = NameToUUID(Namespace_URL, 'http://www.stackoverflow.com');
uuid = NameToUUID(Namespace_URL, 'http://www.google.com/search&q=rfc+4112');
uuid = NameToUUID(Namespace_URL, 'http://stackoverflow.com/questions/5515880/test-vectors-for-uuid-version-5-converting-hash-into-guid-algorithm');

Ahora volvamos a tu pregunta

Para los UUID de la versión 3 y la versión 5, se debe proporcionar el nombre y el espacio de nombres de los argumentos de la línea de comandos adicionales. El espacio de nombres es un UUID en representación de cadena o un identificador para los UUID de espacio de nombres predefinidos internamente (actualmente conocidos son "ns: DNS", "ns: URL", "ns: OID" y "ns: X500"). El nombre es una cadena de longitud arbitraria.

El espacio de nombres es el UUID que desee. Puede ser uno de los predefinidos, o puede crear uno propio, por ejemplo:

UUID Namespace_RectalForeignExtractedObject = '8e884ace-bee4-11e4-8dfc-aa07a5b093db'

El nombre es una cadena de longitud arbitraria.

El nombre es solo el texto que desea agregar al espacio de nombres, luego hash y relleno en un UUID:

uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'screwdriver');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'toothbrush');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'broomstick');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'orange');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'axe handle');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'impulse body spray');
uuid = NameToUUID('8e884ace-bee4-11e4-8dfc-aa07a5b093db', 'iPod Touch');

Nota : Cualquier código publicado en el dominio público. No se requiere atribución.

Ian Boyd
fuente
45
Gracias por la explicación detallada. Si pudiera dar puntos de bonificación, Namespace_RectalForeignExtractedObjectlo haría.
boodle
¿Es posible decodificar el nombre o el espacio de nombres decodificado del UUID?
Sábado
3
@Sathesh No, no es posible decodificar un hash; los hash son funciones unidireccionales. Por ejemplo, la colección completa de Star Trek TNG Blu-Ray es de 81 GB y tiene un hash de C5740BBBF2429115276D4AB60A020ED3ADE01192 . No hay forma de decodificar ese hash de 20 bytes nuevamente en 81 GB. Si realmente lo necesita, puede intentar aplicar hash a todos los GUID y cadenas posibles hasta que encuentre la combinación que dé el mismo resultado. Con cualquier almuerzo lo encontrarás entre la eternidad y la eternidad.
Ian Boyd
22

Un nombre no es más que un identificador único dentro de algún espacio de nombres. El problema es que los espacios de nombres suelen ser bastante pequeños y los nombres de uno suelen colisionar con los de otros. Por ejemplo, el número de placa de mi automóvil (nombre) es único dentro del espacio de nombres del DMV de mi estado, pero probablemente no sea único en el mundo; Es posible que otros DMV estatales hayan usado el mismo nombre en sus propios espacios de nombres. Diablos, alguien más puede tener un número de teléfono (nombre) que también coincide porque ese es otro espacio de nombres, etc.

Se puede considerar que los UUID habitan un solo espacio de nombres tan vasto que puede proporcionar un nombre único para todo ; eso es lo que significa "universal". Pero, ¿cómo se asignan los nombres existentes en otros espacios de nombres a un UUID?

Una solución obvia es generar un UUID (V1 o V4) para cada elemento para reemplazar los nombres antiguos en sus espacios de nombres separados. La desventaja es que son mucho más grandes, tienes que comunicar todos los nombres nuevos a todos los que tienen una copia de tu conjunto de datos, actualizar todas tus API, etc. Lo más probable es que no puedas deshacerte de los nombres antiguos por completo. de todos modos, lo que significa que ahora cada elemento tiene dos nombres, así que, ¿mejoró o empeoró las cosas?

Aquí es donde entra en juego V3 / V5. Los UUID parecen tan aleatorios como V4, pero en realidad son deterministas; Cualquiera que tenga el UUID correcto para un espacio de nombres puede generar de forma independiente el mismo UUID para cualquier nombre dentro de ese espacio de nombres. No es necesario publicarlos ni siquiera generarlos previamente, ya que cualquiera puede crearlos sobre la marcha según sea necesario.

Los nombres DNS y las URL son espacios de nombres de uso muy común, por lo que se publicaron UUID estándar para ellos; Los OID ASN.1 y los nombres X.500 no son tan comunes, pero los organismos de estándares los adoran, por lo que también publicaron UUID de espacio de nombres estándar para ellos.

Para todos los demás espacios de nombres, debe generar su propio UUID de espacio de nombres (V1 o V4) y comunicarlo a cualquiera que lo necesite. Si tiene varios espacios de nombres, tener que publicar el UUID para cada uno claramente no es lo ideal.

Aquí es donde entra la jerarquía: usted crea un UUID "base" (de cualquier tipo), y luego lo usa como un espacio de nombres para nombrar sus otros espacios de nombres. De esa forma, solo tienes que publicar el UUID base (o usar uno obvio), y todos pueden calcular el resto.

Por ejemplo, quedémonos que queríamos crear algunos UUID para StackOverflow; que tiene un nombre obvio dentro del espacio de nombres DNS, por lo que la base es obvia:

uuid ns_dns = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
uuid ns_base = uuidv5(ns_dns, 'stackoverflow.com');

StackOverflow en sí tiene espacios de nombres separados para usuarios, preguntas, respuestas, comentarios, etc., pero esos también son bastante obvios:

uuid ns_user = uuidv5(ns_base, 'user');
uuid ns_question = uuidv5(ns_base, 'question');
uuid ns_answer = uuidv5(ns_base, 'answer');
uuid ns_comment = uuidv5(ns_base, 'comment');

Esta pregunta en particular es # 10867405, por lo que su UUID sería:

uuid here = uuidv5(ns_question, '10867405');

Tenga en cuenta que no hay nada aleatorio en este proceso, por lo que cualquiera que siga la misma lógica obtendrá la misma respuesta, sin embargo, el espacio de nombres UUID es tan vasto que (efectivamente, dada la seguridad de un hash criptográfico de 122 bits) nunca chocará con un UUID generado a partir de cualquier otro espacio de nombres / par de nombres.

StephenS
fuente
Me pregunto por qué stackoverflow necesita mapear un entero grande generado de forma única a un UUID dado que sus API aparentemente devuelven solo el entero grande como una cadena. ¿Dónde se usaría el UUID si no estuviera en la API? ¿Parece que deberíamos seleccionar un UUID o BIGINT? Por qué hacer esta estrategia híbrida. Sin embargo, +1 por la explicación clara en su respuesta.
nishant
4
UUID V3 / V5 se diseñaron para cuando necesita convertir de manera determinista espacios de nombres existentes (y probablemente en colisión) en un espacio de nombres UUID, que a menudo es útil cuando se fusionan conjuntos de datos. Si eso no se aplica a lo que está haciendo, elija V1 / V4.
StephenS