TSQL md5 hash diferente a C#.NET md5
sql-server unicode (4)
Estaba teniendo el mismo problema, y como comenta @srutzky, lo que podría estar sucediendo es que no precedí la consulta con una N mayúscula, y estaba obteniendo un ASCII extendido de 8 bits (VARCHAR / cadena no prefijada con mayúscula -N) en lugar de un Little Endian UTF-16 de 16 bits (NVARCHAR / cadena con el prefijo N mayúscula)
{Id, UserName, PasswordString, PasswordHashed}
Si lo haces:
SELECT TOP 1 CONVERT(char(32),HashBytes(''MD5'', ''abc123''),2) FROM [Users]
Saldrá: E99A18C428CB38D5F260853678922E03
Pero si hace esto, tiene la misma contraseña (''abc123''):
SELECT CONVERT(char(32),HashBytes(''MD5'', [PasswordString]),2) FROM [Users]
Saldrá: 6E9B3A7620AAF77F362775150977EEB8
Lo que debería haber hecho es:
SELECT CONVERT(char(32),HashBytes(''MD5'', N''abc123''),2) FROM [Users]
Eso genera el mismo resultado: 6E9B3A7620AAF77F362775150977EEB8
He generado un hash md5 de la siguiente manera:
DECLARE @varchar varchar(400)
SET @varchar = ''è''
SELECT CONVERT(VARCHAR(2000), HASHBYTES( ''MD5'', @varchar ), 2)
Qué salidas:
785D512BE4316D578E6650613B45E934
Sin embargo, generar un hash MD5 usando:
System.Text.Encoding.UTF8.GetBytes("è")
genera:
0a35e149dbbb2d10d744bf675c7744b1
La codificación en el método C # .NET está establecida en UTF8 y supuse que varchar también era UTF8, ¿alguna idea de lo que estoy haciendo mal?
SQL Server usa UCS-2 en lugar de UTF-8 para codificar datos de caracteres.
Si estuviera utilizando un campo NVarChar, lo siguiente funcionaría:
System.Text.Encoding.Unicode.GetBytes("è"); // Updated per @srutzky''s comments
Para obtener más información sobre el hash de SQL y C #, vea
Si está tratando con datos
NVARCHAR
/
NCHAR
(que se almacenan como
UTF-16 Little Endian
), entonces usaría la codificación
Unicode
, no
BigEndianUnicode
.
En .NET, UTF-16 se llama
Unicode
mientras que otras codificaciones Unicode se denominan por sus nombres reales: UTF7, UTF8 y UTF32.
Por lo tanto,
Unicode
en sí mismo es
Little Endian
en comparación con
BigEndianUnicode
.
ACTUALIZACIÓN:
Consulte la sección al final sobre UCS-2 y caracteres suplementarios.
En el lado de la base de datos:
SELECT HASHBYTES(''MD5'', N''è'') AS [HashBytesNVARCHAR]
-- FAC02CD988801F0495D35611223782CF
En el lado .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 se refiere a los datos
VARCHAR
/
CHAR
, que es 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. De esos valores hash debería haber dos preguntas:
-
¿Por qué
ninguno
de ellos coincide con el valor
HASHBYTES
? -
¿Por qué el artículo "sqlteam.com" vinculado en la respuesta de @Eric J. muestra que tres de ellos (
ASCII
,UTF7
yUTF8
) coinciden con el valorHASHBYTES
?
Hay una respuesta que cubre ambas preguntas: Páginas de códigos. La prueba realizada en el artículo "sqlteam" utilizó caracteres ASCII "seguros" que están en el rango 0 - 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 conjunto extendido que varía según la página de códigos (lo cual tiene sentido ya que esta es la razón para tener páginas de códigos).
Ahora intenta:
SELECT HASHBYTES(''MD5'', ''è'' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes]
-- D1457B72C3FB323A2671125AEF3EAB5D
Eso coincide con el valor hash
ASCII
(y nuevamente, debido a que el artículo / prueba "sqlteam" usó valores en el rango 0 - 127, no vieron ningún cambio al usar
COLLATE
).
Genial, ahora finalmente encontramos una manera de hacer coincidir los datos
VARCHAR
/
CHAR
.
¿Todo bien?
Bueno en realidad no. Echemos un vistazo a lo que realmente estábamos haciendo hash:
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
A
?
?
Solo para verificar, ejecute:
SELECT CHAR(63) AS [WhatIs63?];
-- ?
Ah, entonces el código de la página 1255 no tiene el carácter
è
, ¿se traduce como el favorito de todos
?
.
Pero entonces, ¿por qué coincide con el valor hash MD5 en .NET cuando se usa la codificación ASCII?
¿Podría ser que en realidad no estábamos igualando el valor hash de
è
, sino que estábamos igualando el valor hash de
?
:
SELECT HASHBYTES(''MD5'', ''?'') AS [HashBytesVARCHAR]
-- 0xD1457B72C3FB323A2671125AEF3EAB5D
Sip.
El verdadero
ASCII
caracteres
ASCII
son
solo
los primeros 128 caracteres (valores 0 - 127).
Y como acabamos de ver, el
è
es 232. Entonces, usar la codificación
ASCII
en .NET no es tan útil.
Tampoco estaba usando
COLLATE
en el lado T-SQL.
¿Es posible obtener una mejor codificación en el lado .NET?
Sí, utilizando
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 trabaje 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
Entonces, intentemos la página de códigos 1252:
System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES(''MD5'', ''è'')
// 785D512BE4316D578E6650613B45E934
Woo hoo!
Tenemos una coincidencia para los datos de
VARCHAR
que utilizan nuestra clasificación predeterminada de SQL Server :).
Por supuesto, si los datos provienen de una base de datos o un conjunto de campos con 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 (se usa una página de códigos en muchas intercalaciones, por lo que una intercalación diferente no implica
necesariamente
una página de códigos diferente).
Para ver cuáles son los valores posibles de la página de códigos y a qué cultura / localidad pertenecen, consulte la lista de páginas de códigos here (la lista se encuentra en la sección "Comentarios").
Información adicional relacionada con lo que realmente se almacena en los campos
NVARCHAR
/
NCHAR
:
Se puede almacenar cualquier carácter
UTF-16
(2 o 4 bytes), aunque el comportamiento predeterminado de las funciones integradas supone 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.
El uso de una de estas intercalaciones de Windows que terminan en
_SC
, ya sea especificada 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 puede ver, ni
DATALENGTH
ni
HASHBYTES
se ven afectados.
Para obtener más información, consulte la página de MSDN sobre
Compatibilidad y compatibilidad con Unicode
(específicamente la sección "Caracteres complementarios").
los hashbytes del servidor sql siempre funcionan como System.Text.Encoding.Unicode en caracteres unicode como el árabe persa, ... si usa Utf8.Unicode o Ascii.Unicode Verá la diferencia y si usa Utf8.Unicode el resultado de retorno de sql servidor y c # serán iguales