Cómo crear guías deterministas

En nuestra aplicación, estamos creando archivos Xml con un atributo que tiene un valor Guid. Este valor debe ser coherente entre las actualizaciones de archivos. Entonces, incluso si todo lo demás en el archivo cambia, el valor guid para el atributo debería permanecer igual.

Una solución obvia era crear un diccionario estático con el nombre de archivo y las guías que se usarían para ellos. Luego, cada vez que generamos el archivo, buscamos el diccionario para el nombre del archivo y usamos el guid correspondiente. Pero esto no es factible porque podríamos escalar a cientos de archivos y no queríamos mantener una gran lista de guías.

Entonces, otro enfoque fue hacer que el Guid sea el mismo basado en la ruta del archivo. Dado que nuestras rutas de archivos y la estructura del directorio de la aplicación son únicas, el Guid debe ser exclusivo para esa ruta. Entonces, cada vez que ejecutamos una actualización, el archivo obtiene el mismo guid en función de su ruta. Encontré una forma genial de generar tales ‘ Guias deterministas ‘ (gracias a Elton Stoneman). Básicamente hace esto:

private Guid GetDeterministicGuid(string input) { //use MD5 hash to get a 16-byte hash of the string: MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider(); byte[] inputBytes = Encoding.Default.GetBytes(input); byte[] hashBytes = provider.ComputeHash(inputBytes); //generate a guid from the hash: Guid hashGuid = new Guid(hashBytes); return hashGuid; } 

Entonces, dado un hilo, el Guid siempre será el mismo.

¿Hay otros enfoques o formas recomendadas para hacer esto? ¿Cuáles son los pros o los contras de ese método?

Como se menciona en @bacar, RFC 4122 §4.3 define una forma de crear un UUID basado en nombre. La ventaja de hacer esto (más que usar un hash MD5) es que se garantiza que no colisionarán con UUID sin nombre, y tienen una muy pequeña posibilidad de colisión con otros UUID basados ​​en nombre.

No existe soporte nativo en .NET Framework para crear estos, pero publiqué código en GitHub que implementa el algoritmo. Se puede usar de la siguiente manera:

 Guid guid = GuidUtility.Create(GuidUtility.UrlNamespace, filePath); 

Para reducir aún más el riesgo de colisiones con otros GUID, puede crear un GUID privado para usar como ID de espacio de nombres (en lugar de usar el ID de espacio de nombres de URL definido en el RFC).

Esto convertirá cualquier cadena en un Guid sin tener que importar un ensamblaje externo.

 public static Guid ToGuid(string src) { byte[] stringbytes = Encoding.UTF8.GetBytes(src); byte[] hashedBytes = new System.Security.Cryptography .SHA1CryptoServiceProvider() .ComputeHash(stringbytes); Array.Resize(ref hashedBytes, 16); return new Guid(hashedBytes); } 

Hay formas mucho mejores de generar un Guid único, pero esta es una forma de actualizar consistentemente una clave de datos de cadena a una clave de datos Guid.

Como Rob menciona, su método no genera un UUID, sino que genera un hash que se parece a un UUID.

El RFC 4122 en UUID permite específicamente UUID deterministas (basados ​​en nombre) – Las versiones 3 y 5 usan md5 y SHA1 (respectivamente). La mayoría de la gente probablemente esté familiarizada con la versión 4, que es aleatoria. Wikipedia ofrece una buena visión general de las versiones. (Tenga en cuenta que el uso de la palabra ‘versión’ aquí parece describir un ‘tipo’ de UUID – la versión 5 no reemplaza a la versión 4).

Parece que hay algunas bibliotecas para generar UUID de versión 3/5, incluido el módulo python uuid , boost.uuid (C ++) y el UUID OSSP . (No he buscado ningún .NET)

MD5 es débil, creo que puede hacer lo mismo con SHA-1 y obtener mejores resultados.

Por cierto, solo una opinión personal, vestir un hash md5 como un GUID no lo convierte en un buen GUID. Las GUID por su propia naturaleza no son deterministas. esto se siente como un truco. ¿Por qué no llamar simplemente a spade a spade y simplemente decir que es un hash prestado de la entrada? puedes hacer eso usando esta línea, en lugar de la nueva línea de guía:

 string stringHash = BitConverter.ToString(hashBytes) 

Debe hacer una distinción entre instancias de la clase Guid e identificadores que son globalmente únicos. Una “guía determinista” es en realidad un hash (como lo demuestra su llamada a provider.ComputeHash ). Los valores hash tienen una probabilidad mucho mayor de colisiones (dos cadenas diferentes suceden para producir el mismo hash) que Guid creado a través de Guid.NewGuid .

Entonces, el problema con su enfoque es que tendrá que estar bien con la posibilidad de que dos rutas diferentes produzcan el mismo GUID. Si necesita un identificador único para una cadena de ruta dada, lo más fácil es usar la cadena . Si necesita que los usuarios oculten la cadena, críptica : puede usar ROT13 o algo más poderoso …

Intentar calzar algo que no es un GUID puro en el tipo de datos GUID podría conducir a problemas de mantenimiento en el futuro …