Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Hash TSQL md5 diverso da C# .NET md5

Se hai a che fare con NVARCHAR / NCHAR dati (che sono archiviati come UTF-16 Little Endian ), quindi utilizzeresti Unicode codifica, non BigEndianUnicode . In .NET, UTF-16 è chiamato Unicode mentre altre codifiche Unicode sono indicate con i loro nomi effettivi:UTF7, UTF8 e UTF32. Quindi, Unicode di per sé è Little Endian al contrario di BigEndianUnicode . AGGIORNAMENTO: Consulta la sezione alla fine relativa a UCS-2 e ai caratteri supplementari.

Lato 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 VARCHAR / CHAR data, che è ASCII, e quindi le cose sono un po' più complicate.

Lato database:

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

Vediamo già il lato .NET sopra. Da quei valori hash dovrebbero sorgere due domande:

  • Perché non nessuno di loro corrispondono a HASHBYTES valore?
  • Perché l'articolo "sqlteam.com" collegato nella risposta di @Eric J. mostra che tre di loro (ASCII , UTF7 e UTF8 ) tutti corrispondono a HASHBYTES valore?

C'è una risposta che copre entrambe le domande:Code Pages. Il test eseguito nell'articolo "sqlteam" ha utilizzato caratteri ASCII "sicuri" compresi nell'intervallo 0 - 127 (in termini di valore int/decimale) che non variano tra le pagine codici. Ma l'intervallo 128 - 255 -- dove troviamo il carattere "è" -- è il esteso set che varia in base alla Code Page (il che ha senso in quanto questo è il motivo per avere le Code Page).

Ora prova:

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

Corrisponde a ASCII valore hash (e ancora, poiché l'articolo / test "sqlteam" utilizzava valori nell'intervallo 0 - 127, non hanno visto alcuna modifica quando si utilizza COLLATE ). Ottimo, ora abbiamo finalmente trovato un modo per abbinare VARCHAR / CHAR dati. Tutto bene?

Beh, non proprio. Diamo un'occhiata a cosa stavamo effettivamente effettuando l'hashing:

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];

Resi:

TheChar TheASCIIvalue   CharCP1255  TheASCIIvalueCP1255
è       232             ?           63

Un ? ? Solo per verificare, esegui:

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

Ah, quindi Code Page 1255 non ha il è carattere, quindi viene tradotto come il ? preferito da tutti . Ma allora perché corrispondeva al valore hash MD5 in .NET quando si utilizzava la codifica ASCII? Potrebbe essere che non stavamo effettivamente abbinando il valore hash di è , ma invece corrispondevano 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, usando il ASCII la codifica in .NET non è così utile. Né stava usando COLLATE sul lato T-SQL.

È possibile ottenere una migliore codifica sul lato .NET? Sì, utilizzando Encoding.GetEncoding(Int32), che consente di specificare la tabella codici. La tabella codici da utilizzare può essere individuata utilizzando la query seguente (usa sys.columns quando si lavora con una colonna invece di un valore 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 Pagina codice 1252:

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

Woo hoo! Abbiamo una corrispondenza per VARCHAR dati che utilizzano le nostre regole di confronto SQL Server predefinite :). Ovviamente, se i dati provengono da un database o da un campo impostato su regole di confronto diverse, allora GetEncoding(1252) potrebbe non funziona e dovrai trovare la tabella codici effettiva corrispondente utilizzando la query mostrata sopra (una tabella codici viene utilizzata in molte regole di confronto, quindi non è necessariamente implicano una Code Page diversa).

Per vedere quali sono i possibili valori della Code Page e a quale cultura/località si riferiscono, consulta l'elenco delle Code Page qui (l'elenco si trova nella sezione "Osservazioni").

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

È possibile memorizzare qualsiasi carattere UTF-16 (2 o 4 byte), sebbene il comportamento predefinito delle funzioni integrate presuppone che tutti i caratteri siano UCS-2 (2 byte ciascuno), che è un sottoinsieme di UTF-16. A partire da SQL Server 2012, è possibile accedere a un set di regole di confronto di Windows che supportano i caratteri a 4 byte noti come caratteri supplementari. Utilizzando una di queste regole di confronto di Windows che terminano con _SC , specificato per una colonna o direttamente in una query, consentirà 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];

Resi:

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

Come puoi vedere, né DATALENGTHHASHBYTES sono interessati. Per ulteriori informazioni, vedere la pagina MSDN per le regole di confronto e il supporto Unicode (in particolare la sezione "Caratteri supplementari").