Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Hachage TSQL md5 différent de C# .NET md5

Si vous avez affaire à NVARCHAR / NCHAR données (qui sont stockées en tant que UTF-16 Little Endian ), alors vous utiliseriez le Unicode encodage, pas BigEndianUnicode . Dans .NET, UTF-16 est appelé Unicode tandis que les autres encodages Unicode sont désignés par leurs noms réels :UTF7, UTF8 et UTF32. Par conséquent, Unicode par lui-même est Little Endian par opposition à BigEndianUnicode . MISE À JOUR : Veuillez consulter la section à la fin concernant le SCU-2 et les caractères supplémentaires.

Côté base de données :

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

Côté .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

Cependant, cette question concerne VARCHAR / CHAR data, qui est ASCII, et donc les choses sont un peu plus compliquées.

Côté base de données :

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

On voit déjà le côté .NET ci-dessus. À partir de ces valeurs hachées, il devrait y avoir deux questions :

  • Pourquoi n'en faire aucun d'entre eux correspondent aux HASHBYTES valeur ?
  • Pourquoi l'article "sqlteam.com" lié dans la réponse de @Eric J. montre-t-il que trois d'entre eux (ASCII , UTF7 , et UTF8 ) correspondent tous au HASHBYTES valeur ?

Il existe une réponse qui couvre les deux questions :les pages de code. Le test effectué dans l'article "sqlteam" a utilisé des caractères ASCII "sûrs" compris entre 0 et 127 (en termes de valeur int/decimal) qui ne varient pas entre les pages de code. Mais la plage 128 - 255 -- où l'on trouve le caractère "è" -- est la Extended ensemble qui varie selon la page de code (ce qui est logique car c'est la raison d'avoir des pages de code).

Essayez maintenant :

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

Qui correspond au ASCII valeur hachée (et encore une fois, parce que l'article / le test "sqlteam" utilisait des valeurs comprises entre 0 et 127, ils n'ont vu aucun changement lors de l'utilisation de COLLATE ). Super, maintenant nous avons enfin trouvé un moyen de faire correspondre VARCHAR / CHAR Les données. Tout va bien ?

Eh bien pas vraiment. Jetons un coup d'œil à ce que nous étions en train de hacher :

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

Renvoie :

TheChar TheASCIIvalue   CharCP1255  TheASCIIvalueCP1255
è       232             ?           63

Un ? ? Juste pour vérifier, exécutez :

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

Ah, donc la page de code 1255 n'a pas le è caractère, donc il est traduit par le ? préféré de tout le monde . Mais alors pourquoi cela correspond-il à la valeur hachée MD5 dans .NET lors de l'utilisation de l'encodage ASCII ? Se pourrait-il que nous ne correspondions pas réellement à la valeur hachée de è , mais correspondaient à la place à la valeur hachée de ? :

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

Ouais. Le vrai jeu de caractères ASCII est juste les 128 premiers caractères (valeurs 0 - 127). Et comme nous venons de le voir, le è est 232. Donc, en utilisant le ASCII l'encodage dans .NET n'est pas très utile. N'utilisait pas non plus COLLATE du côté T-SQL.

Est-il possible d'obtenir un meilleur encodage côté .NET ? Oui, en utilisant Encoding.GetEncoding(Int32), qui permet de spécifier la page de code. La page de code à utiliser peut être découverte à l'aide de la requête suivante (utilisez sys.columns lorsque vous travaillez avec une colonne au lieu d'un littéral ou d'une 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 requête ci-dessus renvoie (pour moi) :

Latin1_General_100_CI_AS_SC    1252

Alors, essayons la page de code 1252 :

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

Woo hoo! Nous avons une correspondance pour VARCHAR données qui utilisent notre collation SQL Server par défaut :). Bien sûr, si les données proviennent d'une base de données ou d'un champ défini sur un classement différent, alors GetEncoding(1252) pourrait ne fonctionne pas et vous devrez trouver la page de code correspondante à l'aide de la requête ci-dessus (une page de code est utilisée dans de nombreux classements, donc un autre classement ne le fait pas nécessairement impliquent une page de code différente).

Pour voir quelles sont les valeurs possibles de la page de code et à quelle culture/région elles se rapportent, veuillez consulter la liste des pages de code ici (la liste se trouve dans la section "Remarques").

Informations supplémentaires liées à ce qui est réellement stocké dans NVARCHAR / NCHAR champs :

Tout caractère UTF-16 (2 ou 4 octets) peut être stocké, bien que le comportement par défaut des fonctions intégrées suppose que tous les caractères sont UCS-2 (2 octets chacun), qui est un sous-ensemble de UTF-16. À partir de SQL Server 2012, il est possible d'accéder à un ensemble de classements Windows prenant en charge les caractères de 4 octets appelés caractères supplémentaires. Utiliser l'un de ces classements Windows se terminant par _SC , spécifié pour une colonne ou directement dans une requête, permettra aux fonctions intégrées de gérer correctement les caractères de 4 octets.

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

Renvoie :

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

Comme vous pouvez le voir, ni DATALENGTH ni HASHBYTES sont affectés. Pour plus d'informations, veuillez consulter la page MSDN pour le classement et la prise en charge d'Unicode (en particulier la section "Caractères supplémentaires").