Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Hash TSQL md5 diferente do C# .NET md5


Se você estiver lidando com NVARCHAR / NCHAR dados (que são armazenados como UTF-16 Little Endian ), então você usaria o Unicode codificação, não BigEndianUnicode . Em .NET, UTF-16 é chamado de Unicode enquanto outras codificações Unicode são referidas por seus nomes reais:UTF7, UTF8 e UTF32. Portanto, Unicode por si só é Little Endian ao contrário de BigEndianUnicode . ATUALIZAÇÃO: Por favor, veja a seção no final sobre UCS-2 e caracteres suplementares.

No lado do banco de dados:
SELECT HASHBYTES('MD5', N'è') AS [HashBytesNVARCHAR]
-- FAC02CD988801F0495D35611223782CF

No 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

No entanto, esta questão pertence a VARCHAR / CHAR data, que é ASCII, então as coisas são um pouco mais complicadas.

No lado do banco de dados:
SELECT HASHBYTES('MD5', 'è') AS [HashBytesVARCHAR]
-- 785D512BE4316D578E6650613B45E934

Já vemos o lado .NET acima. A partir desses valores de hash, deve haver duas perguntas:
  • Por que não nenhum deles correspondem aos HASHBYTES valor?
  • Por que o artigo "sqlteam.com" vinculado na resposta de @Eric J. mostra que três deles (ASCII , UTF7 e UTF8 ) todos correspondem aos HASHBYTES valor?

Há uma resposta que cobre ambas as perguntas:Páginas de código. O teste feito no artigo "sqlteam" usou caracteres ASCII "seguros" que estão no intervalo de 0 a 127 (em termos do valor int/decimal) que não variam entre as páginas de código. Mas o intervalo de 128 a 255 -- onde encontramos o caractere "è" -- é o Estendido set que varia de acordo com a página de código (o que faz sentido, pois esse é o motivo de ter páginas de código).

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

Isso corresponde ao ASCII valor com hash (e novamente, porque o artigo / teste "sqlteam" usou valores no intervalo de 0 a 127, eles não viram nenhuma alteração ao usar COLLATE ). Ótimo, agora finalmente encontramos uma maneira de combinar VARCHAR / CHAR dados. Tudo certo?

Bem, na verdade não. Vamos dar uma olhada no que estávamos realmente fazendo 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];

Devoluções:
TheChar TheASCIIvalue   CharCP1255  TheASCIIvalueCP1255
è       232             ?           63

Um ? ? Apenas para verificar, execute:
SELECT CHAR(63) AS [WhatIs63?];
-- ?

Ah, então a página de código 1255 não tem o è personagem, então ele é traduzido como o ? . Mas então por que isso corresponde ao valor de hash MD5 no .NET ao usar a codificação ASCII? Será que não estávamos realmente correspondendo ao valor de hash de è , mas estavam correspondendo ao valor com hash de ? :
SELECT HASHBYTES('MD5', '?') AS [HashBytesVARCHAR]
-- 0xD1457B72C3FB323A2671125AEF3EAB5D

Sim. O verdadeiro conjunto de caracteres ASCII é apenas os primeiros 128 caracteres (valores 0 - 127). E como acabamos de ver, o è é 232. Então, usando o ASCII codificação em .NET não é tão útil. Nem estava usando COLLATE no lado T-SQL.

É possível obter uma codificação melhor no lado .NET? Sim, usando Encoding.GetEncoding(Int32), que permite especificar a página de código. A página de código a ser usada pode ser descoberta usando a seguinte consulta (use sys.columns ao trabalhar com uma coluna em vez de um literal ou variável):
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

A consulta acima retorna (para mim):
Latin1_General_100_CI_AS_SC    1252

Então, vamos tentar a página de código 1252:
System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è')
// 785D512BE4316D578E6650613B45E934

Uau! Temos uma correspondência para VARCHAR data que usa nosso agrupamento padrão do SQL Server :). Obviamente, se os dados vierem de um banco de dados ou conjunto de campos para um agrupamento diferente, GetEncoding(1252) pode não funcionar e você terá que encontrar a página de código correspondente usando a consulta mostrada acima (uma página de código é usada em muitos agrupamentos, portanto, um agrupamento diferente não necessariamente implicar uma página de código diferente).

Para ver quais são os possíveis valores de página de código e a que cultura/localidade eles pertencem, consulte a lista de páginas de código aqui (a lista está na seção "Observações").

Informações adicionais relacionadas ao que está realmente armazenado em NVARCHAR / NCHAR campos:

Qualquer caractere UTF-16 (2 ou 4 bytes) pode ser armazenado, embora o comportamento padrão das funções internas suponha que todos os caracteres sejam UCS-2 (2 bytes cada), que é um subconjunto de UTF-16. A partir do SQL Server 2012, é possível acessar um conjunto de agrupamentos do Windows que dão suporte aos caracteres de 4 bytes conhecidos como caracteres suplementares. Usando um desses agrupamentos do Windows que terminam em _SC , especificado para uma coluna ou diretamente em uma consulta, permitirá que as funções internas manipulem adequadamente os 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];

Devoluções:
SupplementaryChar   LEN   DATALENGTH   UNICODE   LEFT   HASHBYTES
𨝫                  2     4             55393    �     0x7A04F43DA81E3150F539C6B99F4B8FA9
𨝫                  1     4            165739    𨝫     0x7A04F43DA81E3150F539C6B99F4B8FA9

Como você pode ver, nem DATALENGTH nem HASHBYTES são afetados. Para obter mais informações, consulte a página do MSDN para suporte a agrupamento e Unicode (especificamente a seção "Caracteres suplementares").