2012-09-28 15 views
7

Sfondo (È possibile saltare questo paragrafo)SHA1Managed.ComputeHash Di tanto in tanto diverso su server diversi

ho una grande quantità di dati (circa 3 MB) che devono essere tenuti aggiornati su diverse centinaia di macchine. Alcune macchine eseguono C# e alcune eseguono Java. I dati potrebbero cambiare in qualsiasi momento e devono essere propagati ai clienti in pochi minuti. I dati vengono consegnati in formato Json da 4 server con carico bilanciato. Questi 4 server eseguono ASP.NET 4.0 con Mvc 3 e C# 4.0.

Il codice che viene eseguito sui 4 server ha un algoritmo di hash che blocca la risposta Json e quindi converte l'hash in una stringa. Questo hash è dato al cliente. Quindi, ogni pochi minuti, i client eseguono il ping del server con l'hash e se l'hash non è aggiornato viene restituito il nuovo oggetto Json. Se l'hash è ancora attuale, viene restituito un 304 con un corpo empie.

Occasionalmente gli hash generati dalle 4 caselle sono incoerenti tra le caselle, il che significa che i client scaricano costantemente i dati (ogni richiesta potrebbe colpire un server diverso).

pezzo di codice presentato

Qui è il codice che viene utilizzato per generare l'hash.

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

per cercare di eseguire il debug del problema ho diviso fuori in questo modo:

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

Ho poi aggiunto un percorso che sputa fuori i valori sopra indicati e utilizzati Beyond Compare per confrontare le differenze.

array byte vengono convertiti in un formato di stringa per l'uso BeyondCompare utilizzando:

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

Come si può vedere la matrice di byte viene visualizzato letteralmente come una sequenza di byte. Non è "convertito".

Il problema

ho scoperto che i valori Prehash e ASCIIBytes erano gli stessi, ma i valori HashedBytes erano diverse - il che significa che l'hash è stato anche diverso.

Ho riavviato IIS WebSites sulle 4 caselle del server più volte e, quando avevano diversi hash, ha confrontato i valori in BeyondCompare. In everycase era il valore "HashedBytes" che era diverso (i risultati di SHA1Managed.ComputeHash (...))

La questione

Che cosa sto facendo di sbagliato? L'input per la funzione ComputeHash è identico. È dipendente dalla macchina SHA1? Ciò non accade poiché metà del tempo in cui le 4 macchine hanno lo stesso hash.

Ho cercato StackOverFlow e Bing ma non sono riuscito a trovare nessun altro con questo problema. La cosa più vicina che ho trovato sono persone con problemi di codifica, ma penso di aver dimostrato che la codifica non è un problema.

uscita

speravo di non scaricare tutto qui a causa di quanto tempo è, ma qui è uno snipet della discarica mi sto paragonando:

Hash: o1ZxBaVuU6OhE6De96wJXUvmz3M =
HashedBytes: 163861135165110831631611916022224717299375230207115
ASCIIBytes: 115116971031014699111109477911410010111483101114118105991014711510111411810599101461151189959115105103110117112951151011141181059910111410110210111410111099101115959897991071011101001111141001011141151011141181059910195 1185095117114108611041161161125847471051159897991071011101004610910211598101115116971031014699111109477911410010111483101114118105991014711510111411810599101461151189947118505911510510311011711295115101114118105991011141011021011141011109910111595989799107101110100112971211091011101161151161111141011151011141 .... Prehash: ...

Quando metto a confronto le due pagine su diversi server i byte ASCII sono identiche, ma i HashedBytes non lo sono. Il metodo di dumping che utilizzo per i byte non esegue conversioni, semplicemente scarica ogni byte in sequenza. Potrei delimitare i byte con un '.' Credo.

Follow Up ho fatto il cambiamento b.ToString (CultureInfo.InvariantCulture) e hanno fatto il HashAlgorithm una variabile locale invece di una proprietà statica. Sto aspettando che il codice venga distribuito ai server.

+4

Invece di scaricare una stringa e controllarla (che il tuo strumento potrebbe ignorare alcune differenze), scarica i byte e guarda come si confrontano. – CrazyCasta

+0

I 'HashedBytes' sono esattamente gli stessi? –

+0

@ Mike No, sta dicendo che sono diversi. – CrazyCasta

risposta

9

Ho cercato di duplicare il problema ma non sono riuscito a farlo una volta che ho reso la proprietà SHA1Managed una variabile locale anziché statica globale.

Il problema era con il multi-threading. Il mio codice era thread-safe tranne per la classe SHA1Managed che avevo contrassegnato come statica. Supponevo che SHA1Managed.ComputeHash fosse al di sotto del thread, ma a quanto pare non lo è se è contrassegnato come interno statico.

Per ripetere, SHA1Managed.ComputeHash non è thread-safe se contrassegnato come interno statico.

MSDN afferma:

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

non so il motivo per cui interno statico si comporta in modo diverso rispetto public static.

Contrassegnerei @pst come risposta e aggiungere un commento per chiarire il problema, ma @pst ha fatto un commento in modo da non poterlo contrassegnare come risposta.

Grazie per tutti i vostri input.

+5

'SHA1Managed.ComputeHash' è un metodo di istanza, non un metodo statico, quindi non è possibile chiamare in sicurezza quel metodo sulla stessa istanza' SHA1Managed' da più thread contemporaneamente. Non importa se 'Hasher' è pubblico o interno. –

+0

Anche questo problema mi ha colpito, grazie per aver pubblicato. – fabspro

+0

Per risolvere il problema, ho appena aggiunto un blocco (sha1obj) {} attorno al codice. Non ho fatto test per vedere se il blocco è più lento rispetto alla creazione di più istanze o meno, ma non è un problema per il mio caso d'uso. – fabspro

0

Il metodo GetString potrebbe potenzialmente produrre risultati diversi su macchine di culture diverse, perché StringBuilder.Append (byte) chiama byte.ToString (CultureInfo.CurrentCulture). Prova

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

Ma utilizzare un metodo che non utilizza le rappresentazioni di stringa decimale dei valori di byte sarebbe meglio.

0

Il problema è che il tuo codice è probabilmente in disordine con i primi 0, usa il seguente come array per il codice stringa da confrontare. produrrà risultati affidabili ed è specificamente progettato per trasformare array di byte in stringhe in modo che possano essere trasmessi tra le macchine.

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(); 
} 

Inoltre, vi consiglio di verificare che il JSON non è sottigliezza diverso, come quello creerebbe un hash totalmente differente. Ad esempio alcune culture rappresentano il numero "Mille seicento virgola sette" come 1,600.7, 1 000.7 o anche 1 600,7 (vedere la pagina this Wikipedia).

Problemi correlati