¿Cómo creo un HashCode en .net (c #) para una cadena que es segura de almacenar en una base de datos?

Para citar las Pautas y reglas para GetHashCode por Eric Lippert:

Regla: los consumidores de GetHashCode no pueden confiar en que sea estable en el tiempo o en aplicaciones.

Supongamos que tiene un objeto Cliente que tiene varios campos como Nombre, Dirección, etc. Si crea dos objetos con exactamente los mismos datos en dos procesos diferentes, no es necesario que devuelvan el mismo código hash. Si crea un objeto de este tipo el martes en un proceso, lo apaga y lo vuelve a ejecutar el miércoles, los códigos hash pueden ser diferentes.

Esto ha mordido a la gente en el pasado. La documentación de System.String.GetHashCode señala específicamente que dos cadenas idénticas pueden tener diferentes códigos hash en diferentes versiones del CLR, y de hecho lo hacen. No almacene valores hash de cadena en bases de datos y espere que sean los mismos para siempre, porque no lo serán.

Entonces, ¿cuál es la forma correcta de crear un HashCode de una cadena que puedo almacenar en una base de datos?

(¡Por favor dime que no soy la primera persona que ha dejado este error en el software que he escrito!)

Depende de qué propiedades desee que tenga el hash. Por ejemplo, podrías escribir algo como esto:

public int HashString(string text) { // TODO: Determine nullity policy. unchecked { int hash = 23; foreach (char c in text) { hash = hash * 31 + c; } return hash; } } 

Mientras documentes que así es como se calcula el hash, eso es válido. De ninguna manera es criptográficamente seguro ni nada por el estilo, pero puedes persistir sin problemas. Dos cadenas que son absolutamente iguales en el sentido ordinal (es decir, sin igualdad cultural, etc. aplicadas, exactamente el mismo carácter por carácter) producirán el mismo hash con este código.

Los problemas surgen cuando confías en el hash no documentado , es decir, algo que obedece a GetHashCode() pero de ninguna manera se garantiza que permanezca igual de una versión a otra … como string.GetHashCode() .

Escribir y documentar su propio hash de esta manera es como decir: “Esta información delicada está codificada con MD5 (o lo que sea)”. Siempre y cuando sea un hash bien definido, está bien.

EDITAR: Otras respuestas han sugerido usar hashes criptográficos como SHA-1 o MD5. Diría que hasta que sepamos que hay un requisito de seguridad criptográfica en lugar de solo estabilidad, no tiene sentido pasar por el rollo de convertir la cadena en una matriz de bytes y eso. Por supuesto, si el hash está destinado a ser utilizado para cualquier cosa relacionada con la seguridad, un hash estándar de la industria es exactamente lo que debe alcanzar. Pero eso no fue mencionado en ninguna parte de la pregunta.

Aquí hay una reimplementación de la forma actual .NET calcula su código hash de cadena para sistemas de 64 bits . Esto no utiliza punteros como el GetHashCode() real, por lo que será un poco más lento, pero lo hace más resistente a los cambios internos en la string , esto dará un código hash distribuido más uniformemente que la versión de Jon Skeet, que puede resultar en una mejor tiempos de búsqueda en los diccionarios.

 public static class StringExtensionMethods { public static int GetStableHashCode(this string str) { unchecked { int hash1 = 5381; int hash2 = hash1; for(int i = 0; i < str.Length && str[i] != '\0'; i += 2) { hash1 = ((hash1 << 5) + hash1) ^ str[i]; if (i == str.Length - 1 || str[i+1] == '\0') break; hash2 = ((hash2 << 5) + hash2) ^ str[i+1]; } return hash1 + (hash2*1566083941); } } } 

La respuesta es simplemente escribir su propia función hash. Puede encontrar la fuente de algunos siguiendo los enlaces del artículo publicado en los comentarios. O puede usar una función hash incorporada originalmente para criptografía (MD5, SHA1, etc.) y simplemente no usar todos los bits.