TSQL md5 hash diverso da C # .NET md5

Ho generato un hash MD5 come di seguito:

DECLARE @varchar varchar(400) SET @varchar = 'è' SELECT CONVERT(VARCHAR(2000), HASHBYTES( 'MD5', @varchar ), 2) 

Quali uscite:

 785D512BE4316D578E6650613B45E934 

Tuttavia generando un hash MD5 usando:

 System.Text.Encoding.UTF8.GetBytes("è") 

genera:

 0a35e149dbbb2d10d744bf675c7744b1 

La codifica nel metodo C # .NET è impostata su UTF8 e ho pensato che varchar fosse anche UTF8, qualche idea su cosa sto facendo male?

Se si hanno a che fare con dati NVARCHAR / NCHAR (che sono memorizzati come Little Endian UTF-16 ), si utilizzerà la codifica Unicode , non BigEndianUnicode . In .NET, UTF-16 viene chiamato Unicode mentre altre codifiche Unicode vengono indicate con i loro nomi effettivi: UTF7, UTF8 e UTF32. Quindi, Unicode per sé è Little Endian in contrapposizione a BigEndianUnicode . AGGIORNAMENTO: Si prega di consultare la sezione alla fine relativa a UCS-2 e caratteri supplementari.

Dal lato del database:

 SELECT HASHBYTES('MD5', N'è') AS [HashBytesNVARCHAR] -- FAC02CD988801F0495D35611223782CF 

Sul lato .NET:

 System.Text.Encoding.ASCII.GetBytes("è") // D1457B72C3FB323A2671125AEF3EAB5D System.Text.Encoding.UTF7.GetBytes("è") // F63A0999FE759C5054613DDE20346193 System.Text.Encoding.UTF8.GetBytes("è") // 0A35E149DBBB2D10D744BF675C7744B1 System.Text.Encoding.UTF32.GetBytes("è") // 86D29922AC56CF022B639187828137F8 System.Text.Encoding.BigEndianUnicode.GetBytes("è") // 407256AC97E4C5AEBCA825DEB3D2E89C System.Text.Encoding.Unicode.GetBytes("è") // this one matches HASHBYTES('MD5', N'è') // FAC02CD988801F0495D35611223782CF 

Tuttavia, questa domanda riguarda i dati VARCHAR / CHAR , che è ASCII, e quindi le cose sono un po ‘più complicate.

Dal lato del database:

 SELECT HASHBYTES('MD5', 'è') AS [HashBytesVARCHAR] -- 785D512BE4316D578E6650613B45E934 

Vediamo già il lato .NET sopra. Da questi valori hash ci dovrebbero essere due domande:

  • Perché nessuno di essi corrisponde al valore di HASHBYTES ?
  • Perché l’articolo “sqlteam.com” collegato nella risposta di @Eric J. mostra che tre di essi ( ASCII , UTF7 e UTF8 ) corrispondono tutti al valore HASHBYTES ?

C’è una risposta che copre entrambe le domande: Code Pages. Il test eseguito nell’articolo “sqlteam” utilizzava caratteri ASCII “sicuri” compresi nell’intervallo 0 – 127 (in termini di valore int / decimale) che non variano tra le Code Pages. Ma la gamma 128 – 255 – dove troviamo il carattere “è” – è il set esteso che varia a seconda della Code Page (che ha senso in quanto questo è il motivo per avere Code Pages).

Ora prova:

 SELECT HASHBYTES('MD5', 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes] -- D1457B72C3FB323A2671125AEF3EAB5D 

Corrisponde al valore hash ASCII (e ancora, poiché l’articolo / test “sqlteam” ha utilizzato valori nell’intervallo 0 – 127, non hanno visto alcuna modifica quando si utilizza COLLATE ). Bene, ora abbiamo finalmente trovato un modo per abbinare i dati VARCHAR / CHAR . Tutto bene?

Beh, non proprio. Diamo un’occhiata: guarda cosa stavamo effettivamente tritando:

 SELECT 'è' AS [TheChar], ASCII('è') AS [TheASCIIvalue], 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS AS [CharCP1255], ASCII('è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [TheASCIIvalueCP1255]; 

Ritorna:

 TheChar TheASCIIvalue CharCP1255 TheASCIIvalueCP1255 è 232 ? 63 

A ? ? Solo per verificare, esegui:

 SELECT CHAR(63) AS [WhatIs63?]; -- ? 

Ah, quindi Code Page 1255 non ha il carattere è , quindi viene tradotto come preferito da tutti ? . Ma allora perché ha abbinato il valore hash MD5 in .NET quando si utilizza la codifica ASCII? Potrebbe essere che in realtà non stavamo corrispondendo al valore hash di è , ma invece corrispondevamo al valore hash di ? :

 SELECT HASHBYTES('MD5', '?') AS [HashBytesVARCHAR] -- 0xD1457B72C3FB323A2671125AEF3EAB5D 

Sì. Il vero set di caratteri ASCII è solo i primi 128 caratteri (valori 0 – 127). E come abbiamo appena visto, il è è 232. Quindi, usare la codifica ASCII in .NET non è così utile. Né stava usando COLLATE sul lato T-SQL.

È ansible ottenere una codifica migliore sul lato .NET? Sì, utilizzando Encoding.GetEncoding (Int32) , che consente di specificare la tabella codici. La sys.columns codici da utilizzare può essere rilevata utilizzando la query seguente (utilizzare sys.columns quando si lavora con una colonna anziché una variabile letterale o variabile):

 SELECT sd.[collation_name], COLLATIONPROPERTY(sd.[collation_name], 'CodePage') AS [CodePage] FROM sys.databases sd WHERE sd.[name] = DB_NAME(); -- replace function with N'{db_name}' if not running in the DB 

La query sopra restituisce (per me):

 Latin1_General_100_CI_AS_SC 1252 

Quindi, proviamo Code Page 1252:

 System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è') // 785D512BE4316D578E6650613B45E934 

Woo hoo! Abbiamo una corrispondenza per i dati VARCHAR che utilizza le regole di confronto predefinite di SQL Server :). Ovviamente, se i dati provengono da un database o un campo impostato su un confronto diverso, GetEncoding(1252) potrebbe non funzionare e dovrai trovare la pagina di codice corrispondente utilizzando la query mostrata sopra (una pagina codice viene utilizzata in molte regole di confronto, quindi una raccolta diversa non implica necessariamente una pagina di codice diversa).

Per vedere quali sono i possibili valori di Code Page e quali culture / aree si riferiscono, vedere l’elenco delle Code Pages qui (l’elenco è nella sezione “Note”).


Informazioni aggiuntive relative a ciò che è effettivamente memorizzato nei campi NVARCHAR / NCHAR :

È ansible memorizzare qualsiasi carattere UTF-16 (2 o 4 byte), sebbene il comportamento predefinito delle funzioni predefinite presupponga che tutti i caratteri siano UCS-2 (2 byte ciascuno), che è un sottoinsieme di UTF-16. A partire da SQL Server 2012, è ansible accedere a una serie di regole di confronto di Windows che supportano i caratteri a 4 byte noti come Caratteri supplementari. L’utilizzo di una di queste regole di confronto di Windows che termina con _SC , specificato per una colonna o direttamente in una query, consente alle funzioni integrate di gestire correttamente i caratteri a 4 byte.

 -- The database's collation is set to: SQL_Latin1_General_CP1_CI_AS SELECT N'𨝫' AS [SupplementaryCharacter], LEN(N'𨝫') AS [LEN], DATALENGTH(N'𨝫') AS [DATALENGTH], UNICODE(N'𨝫') AS [UNICODE], LEFT(N'𨝫', 1) AS [LEFT], HASHBYTES('MD5', N'𨝫') AS [HASHBYTES]; SELECT N'𨝫' AS [SupplementaryCharacter], LEN(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [LEN], DATALENGTH(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [DATALENGTH], UNICODE(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [UNICODE], LEFT(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC, 1) AS [LEFT], HASHBYTES('MD5', N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [HASHBYTES]; 

Ritorna:

 SupplementaryChar LEN DATALENGTH UNICODE LEFT HASHBYTES 𨝫 2 4 55393   0x7A04F43DA81E3150F539C6B99F4B8FA9 𨝫 1 4 165739 𨝫 0x7A04F43DA81E3150F539C6B99F4B8FA9 

Come puoi vedere, né DATALENGTHHASHBYTES sono interessati. Per ulteriori informazioni, consultare la pagina MSDN per Collation e supporto Unicode (in particolare la sezione “Caratteri supplementari”).

SQL Server utilizza UCS-2 anziché UTF-8 per codificare i dati dei caratteri.

Se si stesse utilizzando un campo NVarChar, quanto segue funzionerebbe:

 System.Text.Encoding.Unicode.GetBytes("è"); // Updated per @srutzky's comments 

Per ulteriori informazioni su hashing SQL e C #, vedere

http://weblogs.sqlteam.com/mladenp/archive/2009/04/28/Comparing-SQL-Server-HASHBYTES-function-and-.Net-hashing.aspx

Stavo avendo lo stesso problema, e come commenti @srutzky, quello che potrebbe accadere è che non ho preceduto la query con un maiuscole-N, e stavo ottenendo un ASCII esteso a 8 bit (VARCHAR / stringa non prefissato con maiuscolo -N) invece di un Little Endian a 16 bit UTF-16 (NVARCHAR / stringa preceduta da maiuscola-N)

 {Id, UserName, PasswordString, PasswordHashed} 

Se fate:

 SELECT TOP 1 CONVERT(char(32),HashBytes('MD5', 'abc123'),2) FROM [Users] 

Produrrà: E99A18C428CB38D5F260853678922E03

Ma se lo fai, con la stessa password (‘abc123’):

 SELECT CONVERT(char(32),HashBytes('MD5', [PasswordString]),2) FROM [Users] 

Produrrà: 6E9B3A7620AAF77F362775150977EEB8

Quello che avrei dovuto fare è:

 SELECT CONVERT(char(32),HashBytes('MD5', N'abc123'),2) FROM [Users] 

Questo produce lo stesso risultato: 6E9B3A7620AAF77F362775150977EEB8

hashbytes del server SQL funziona sempre come System.Text.Encoding.Unicode su caratteri unicode come arabo persiano, … se usi Utf8.Unicode o Ascii.Unicode Vedrai la diffrence e se usi Utf8.Unicode il risultato di ritorno di sql server e c # saranno uguali