sql >> Base de Datos >  >> RDS >> Sqlserver

Hash TSQL md5 diferente a C# .NET md5

Si está tratando con NVARCHAR / NCHAR datos (que se almacenan como UTF-16 Little Endian ), entonces usaría el Unicode codificación, no BigEndianUnicode . En .NET, UTF-16 se llama Unicode mientras que otras codificaciones Unicode se conocen por sus nombres reales:UTF7, UTF8 y UTF32. Por lo tanto, Unicode por sí mismo es Little Endian a diferencia de BigEndianUnicode . ACTUALIZACIÓN: Consulte la sección al final sobre UCS-2 y caracteres complementarios.

En el lado de la base de datos:

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

En el lado de .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

Sin embargo, esta pregunta pertenece a VARCHAR / CHAR datos, que son ASCII, por lo que las cosas son un poco más complicadas.

En el lado de la base de datos:

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

Ya vemos el lado .NET arriba. A partir de esos valores hash, debería haber dos preguntas:

  • ¿Por qué no ninguna de ellos coinciden con los HASHBYTES valor?
  • ¿Por qué el artículo "sqlteam.com" vinculado en la respuesta de @Eric J. muestra que tres de ellos (ASCII , UTF7 y UTF8 ) todos coinciden con HASHBYTES valor?

Hay una respuesta que cubre ambas preguntas:las páginas de códigos. La prueba realizada en el artículo "sqlteam" usó caracteres ASCII "seguros" que están en el rango de 0 a 127 (en términos del valor int/decimal) que no varían entre las páginas de códigos. Pero el rango 128 - 255, donde encontramos el carácter "è", es el Extendido conjunto que varía según la página de códigos (lo cual tiene sentido ya que esta es la razón por la que se tienen páginas de códigos).

Ahora intenta:

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

Que coincida con el ASCII valor hash (y nuevamente, debido a que el artículo / prueba "sqlteam" usó valores en el rango de 0 a 127, no vieron ningún cambio al usar COLLATE ). Genial, ahora finalmente encontramos una manera de hacer coincidir VARCHAR / CHAR datos. ¿Todo bien?

Bueno en realidad no. Echemos un vistazo, veamos lo que realmente estábamos procesando:

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

Devoluciones:

TheChar TheASCIIvalue   CharCP1255  TheASCIIvalueCP1255
è       232             ?           63

¿Un ? ? Solo para verificar, ejecuta:

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

Ah, entonces la página de códigos 1255 no tiene el è carácter, por lo que se traduce como el ? favorito de todos . Pero entonces, ¿por qué eso coincidió con el valor hash MD5 en .NET al usar la codificación ASCII? ¿Podría ser que en realidad no coincidiéramos con el valor hash de è? , sino que coincidían con el valor hash de ? :

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

Sí. El verdadero conjunto de caracteres ASCII es simplemente los primeros 128 caracteres (valores 0 - 127). Y como acabamos de ver, el è es 232. Entonces, usando el ASCII la codificación en .NET no es tan útil. Tampoco estaba usando COLLATE en el lado de T-SQL.

¿Es posible obtener una mejor codificación en el lado .NET? Sí, mediante el uso de Encoding.GetEncoding(Int32), que permite especificar la página de códigos. La página de códigos a usar se puede descubrir usando la siguiente consulta (use sys.columns cuando se trabaja con una columna en lugar de un literal o variable):

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 consulta anterior devuelve (para mí):

Latin1_General_100_CI_AS_SC    1252

Por lo tanto, probemos la página de códigos 1252:

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

¡Guau! Tenemos una coincidencia para VARCHAR datos que utilizan nuestra intercalación predeterminada de SQL Server :). Por supuesto, si los datos provienen de una base de datos o un conjunto de campos en una intercalación diferente, entonces GetEncoding(1252) podría no funcionará y tendrá que encontrar la página de códigos coincidente real utilizando la consulta que se muestra arriba (una página de códigos se usa en muchas intercalaciones, por lo que una intercalación diferente no necesariamente implica una página de códigos diferente).

Para ver cuáles son los posibles valores de la página de códigos y a qué cultura o región pertenecen, consulte la lista de páginas de códigos aquí (la lista se encuentra en la sección "Comentarios").

Información adicional relacionada con lo que realmente se almacena en NVARCHAR / NCHAR campos:

Se puede almacenar cualquier carácter UTF-16 (2 o 4 bytes), aunque el comportamiento predeterminado de las funciones integradas asume que todos los caracteres son UCS-2 (2 bytes cada uno), que es un subconjunto de UTF-16. A partir de SQL Server 2012, es posible acceder a un conjunto de intercalaciones de Windows que admiten los caracteres de 4 bytes conocidos como caracteres complementarios. Usando una de estas intercalaciones de Windows que terminan en _SC , ya sea especificado para una columna o directamente en una consulta, permitirá que las funciones integradas manejen adecuadamente los caracteres de 4 bytes.

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

Devoluciones:

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

Como puedes ver, ni DATALENGTH ni HASHBYTES Son afectados. Para obtener más información, consulte la página de MSDN para Compatibilidad con intercalación y Unicode (específicamente la sección "Caracteres complementarios").