2011-03-01 15 views
38

citar Guidelines and rules for GetHashCode por Eric Lippert:¿Cómo creo un HashCode en .net (C#) para una cadena que es segura de almacenar en una base de datos?

Regla: Los consumidores de GetHashCode no pueden confiar en que sea estable a lo largo del tiempo o entre dominios de aplicación

Suponga que tiene un objeto Cliente que tiene un montón de campos como Nombre, Dirección, etc. Si crea dos objetos con exactamente los mismos datos en dos procesos diferentes, no tienen que devolver el mismo código hash . Si hace ese objeto en martes en un proceso, apáguelo, y ejecute el programa nuevamente en Miércoles, los códigos hash pueden ser diferentes.

Esto ha mordido a personas en el pasado. La documentación para 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!)

+2

Bueno, nunca confío en GetHashCode, porque sé lo descuidado que implemento este método. Creo que otros no lo están haciendo mejor ... ;-) –

+3

No eres la primera persona que ha dejado este error en el software que has escrito. – Bobby

+2

Los motores Dbase ya son muy buenos en las cadenas hash. Solo crea un índice para la columna. –

Respuesta

64

Depende de qué propiedades desee que tenga el hash. Por ejemplo, usted podría acaba de 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; 
    } 
} 

Siempre y cuando usted documento 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 carácter por carácter) generarán el mismo hash con este código.

Los problemas vienen cuando usted confía en indocumentado hash - es decir, algo que obedece GetHashCode(), pero no es de ninguna manera garantizada de seguir siendo el mismo de una versión a ... 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 hash 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 es destinado a ser utilizado para cualquier cosa relacionada con la seguridad, un hash estándar de la industria es exactamente a lo que debe llegar. Pero eso no fue mencionado en ninguna parte de la pregunta.

+3

¿Hay algo mágico sobre 23 y '* 31'? Más bien, ¿hay alguna razón para elegir esos valores por encima de cualquier otro? ... sobre cualquier otro método hash [documentado]? Supongo que no, aunque 31 es uno menos que los imprimibles ASCII me ha mantenido innecesariamente sospechoso. – ruffin

+10

@ruffin: Son valores recomendados por Josh Bloch. Multiplicar por 31 es eficiente porque se puede hacer como un cambio y un restar. Hay varias otras preguntas hablando de esto: para ser sincero, es un arte oscuro. –

+15

¡Aseado! De [Effective Java (2008), página 48] (https://books.google.com/books?id=ka2VUBqHiWkC): * Se eligió el valor 31 porque es un primo impar. Si fuera par y la multiplicación se desbordara, la información se perdería, ya que la multiplicación es equivalente a un cambio. La ventaja de usar un primo es menos clara, pero es tradicional. Una buena propiedad de 31 es que la multiplicación se puede reemplazar por un cambio y una resta para un mejor rendimiento: '31 * i == (i << 5) - i'. Las máquinas virtuales modernas realizan este tipo de optimización automáticamente. * Parece una lectura divertida; gracias de nuevo. – ruffin

1

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.

6

Aquí hay una reimplementación de the current way .NET calculates it's string hash code for 64 bit systems. 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 a string, esto dará un código hash más uniformemente distribuido que Jon Skeet's version que puede dar como resultado mejores 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); 
     } 
    } 
} 
Cuestiones relacionadas