2012-09-28 38 views
7

Antecedentes (Puede omitir esta sección)SHA1Managed.ComputeHash diferente de vez en cuando en diferentes servidores

tengo una gran cantidad de datos (alrededor de 3 MB) que debe mantenerse al tanto de varios cientos de máquinas. Algunas de las máquinas ejecutan C# y algunas ejecutan Java. Los datos podrían cambiar en cualquier momento y deben ser propocionados a los clientes en cuestión de minutos. Los datos se entregan en formato Json desde 4 servidores con equilibrio de carga. Estos 4 servidores ejecutan ASP.NET 4.0 con Mvc 3 y C# 4.0.

El código que se ejecuta en los 4 servidores tiene un algoritmo hash que mezcla la respuesta Json y luego convierte el hash en una cadena. Este hash se le da al cliente. Luego, cada pocos minutos, los clientes hacen ping al servidor con el hash y si el hash está desactualizado, se devuelve el nuevo objeto Json. Si el hash sigue siendo actual, se devuelve un 304 con un cuerpo emptry.

De vez en cuando los hashes generados por los 4 cuadros son inconsistentes en todos los cuadros, lo que significa que los clientes están constantemente descargando los datos (cada solicitud podría afectar a un servidor diferente).

Código snipet

Este es el código que se utiliza para generar el hash.

internal static HashAlgorithm Hasher { get; set; } 
... 
Hasher = new SHA1Managed(); 
... 
Convert.ToBase64String(Hasher.ComputeHash(Encoding.ASCII.GetBytes(jsonString))); 

para tratar de depurar el problema Me dividir a cabo de esta manera:

Prehash = PreHashBuilder.ToString(); 
ASCIIBytes = Encoding.ASCII.GetBytes(Prehash); 
HashedBytes = Hasher.ComputeHash(ASCIIBytes); 
Hash = Convert.ToBase64String(HashedBytes); 

Luego añade un recorrido que escupe los valores anteriores y que no tiene comparación utilizaron para comparar las diferencias.

matrices de bytes se convierten a un formato de cadena para su uso mediante el uso de BeyondCompare:

private static string GetString(byte[] bytes) 
{ 
    StringBuilder sb = new StringBuilder(); 
    foreach (byte b in bytes) 
    { 
     sb.Append(b); 
    } 
    return sb.ToString(); 
} 

Como se puede ver el conjunto de bytes se muestra literalmente como una secuencia de bytes. No es 'convertido'.

El problema

he descubierto que los valores pre-hash y ASCIIBytes eran los mismos, pero los valores HashedBytes eran diferentes - que significaba que el Hash también era diferente.

He reiniciado IIS WebSites en los 4 cuadros de servidor varias veces y, cuando tenían diferentes valores hash, comparé los valores en BeyondCompare. En everycase que era el valor "HashedBytes" que era diferente (los resultados de SHA1Managed.ComputeHash (...))

La pregunta

¿Qué estoy haciendo mal? La entrada a la función ComputeHash es idéntica. ¿Es SHA1Managed Machine dependiente? Eso no ocurre porque la mitad del tiempo las 4 máquinas tienen el mismo hash.

He buscado StackOverFlow y Bing pero no he podido encontrar a nadie más con este problema. Lo más cercano que pude encontrar fue a personas con problemas con su codificación, pero creo que he demostrado que la codificación no es un problema.

salida

Tenía la esperanza de no volcar todo aquí debido a lo largo que es, pero aquí es una snipet del vertedero estoy comparando:

Hash: o1ZxBaVuU6OhE6De96wJXUvmz3M =
HashedBytes: 163861135165110831631611916022224717299375230207115
ASCIIBytes: 115116971031014699111109477911410010111483101114118105991014711510111411810599101461151189959115105103110117112951151011141181059910111410110210111410111099101115959897991071011101001111141001011141151011141181059910195 1185095117114108611041161161125847471051159897991071011101004610910211598101115116971031014699111109477911410010111483101114118105991014711510111411810599101461151189947118505911510510311011711295115101114118105991011141011021011141011109910111595989799107101110100112971211091011101161151161111141011151011141 .... pre-hash: ...

Cuando comparo las dos páginas de los diferentes servidores los bytes ASCII son idénticos, pero los HashedBytes no lo son. El método de volcado que uso para los bytes no genera conversiones, simplemente vacía cada byte en secuencia. Podría delimitar los bytes con un '.' Supongo.

Seguimiento he hecho el cambio b.ToString (CultureInfo.InvariantCulture) haciendo de él la HashAlgorithm una variable local en lugar de una propiedad estática. Estoy esperando que el código se despliegue en los servidores.

+4

En lugar de volcar una cadena y verificarla (que su herramienta puede ignorar para ciertas diferencias) volcar los bytes y ver cómo se comparan. – CrazyCasta

+0

¿Son los 'HashedBytes' exactamente iguales? –

+0

@Mike No, él dice que son diferentes. – CrazyCasta

Respuesta

9

He intentado duplicar el problema pero no he podido hacerlo una vez que hice de la propiedad SHA1Managed una variable local en lugar de global.

El problema era con Multi-Threading. Mi código era seguro para subprocesos a excepción de la clase SHA1Managed que tenía marcada estática. Supuse que SHA1Managed.ComputeHash sería seguro para subprocesos debajo pero aparentemente no lo es si está marcado como estático interno.

Para repetir, SHA1Managed.ComputeHash no es seguro para subprocesos si está marcado como estático interno.

MSDN indica:

Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe. 

no sé qué estática interna se comporta de manera diferente a public static.

Marcaría @pst como la respuesta y añadiría un comentario para aclarar el problema, pero @pst hizo un comentario por lo que no puedo marcarlo como la respuesta.

Gracias por su aportación.

+5

'SHA1Managed.ComputeHash' es un método de instancia, no un método estático, por lo que no puede llamar con seguridad a ese método en la misma instancia' SHA1Managed' de múltiples hilos simultáneamente. No importa si 'Hasher' es público o interno. –

+0

Este problema exacto también me sorprendió, gracias por publicar. – fabspro

+0

Para resolverlo, acabo de agregar un bloqueo (sha1obj) {} alrededor del código. No he hecho pruebas para ver si el bloqueo es más lento que crear instancias múltiples o no, pero no es un problema para mi caso de uso. – fabspro

0

Su método GetString podría producir resultados diferentes en máquinas de diferentes culturas, porque StringBuilder.Append (byte) llama a byte.ToString (CultureInfo.CurrentCulture). Pruebe

private static string GetString(byte[] bytes) 
{ 
    StringBuilder sb = new StringBuilder(); 
    foreach (byte b in bytes) 
    { 
     sb.Append(b.ToString(CultureInfo.InvariantCulture)); 
    } 
    return sb.ToString(); 
} 

Pero sería mejor usar un método que no utilice representaciones de cadenas decimales de los valores de bytes.

0

El problema es que su código probablemente está jugando con los principales 0, use lo siguiente como matriz para comparar el código de cadena. producirá resultados confiables y está diseñado específicamente para convertir matrices de bytes en cadenas para que puedan transmitirse entre máquinas.

using System.Runtime.Remoting.Metadata.W3cXsd2001; 

public byte[] StringToBytes(string value) 
{ 
    SoapHexBinary soapHexBinary = SoapHexBinary.Parse(value); 
    return soapHexBinary.Value; 
} 

public string BytesToString(byte[] value) 
{ 
    SoapHexBinary soapHexBinary = new SoapHexBinary(value); 
    return soapHexBinary.ToString(); 
} 

Además, yo recomendaría que compruebe que el JSON no está sutileza diferente, ya que eso crearía un hash totalmente diversa. Por ejemplo, algunas culturas representan el número "Mil seiscientos punto siete" como 1,600.7, 1 000.7, o incluso 1 600,7 (consulte la página this Wikipedia).

Cuestiones relacionadas