TSQL md5哈希与C#.NET md5不同

Imr*_*zad 16 .net t-sql sql-server unicode encoding

我生成了一个md5哈希,如下所示:

DECLARE @varchar varchar(400) 

SET @varchar = 'è'

SELECT CONVERT(VARCHAR(2000), HASHBYTES( 'MD5', @varchar ), 2)
Run Code Online (Sandbox Code Playgroud)

哪个输出:

785D512BE4316D578E6650613B45E934
Run Code Online (Sandbox Code Playgroud)

但是使用以下方法生成MD5哈希:

System.Text.Encoding.UTF8.GetBytes("è")
Run Code Online (Sandbox Code Playgroud)

产生:

0a35e149dbbb2d10d744bf675c7744b1
Run Code Online (Sandbox Code Playgroud)

C#.NET方法中的编码设置为UTF8,我假设varchar也是UTF8,任何关于我做错的想法?

Sol*_*zky 34

如果您正在处理NVARCHAR/ NCHARdata(存储为UTF-16 Little Endian),那么您将使用Unicode编码,而不是BigEndianUnicode.在.NET中,调用UTF-16,Unicode而其他Unicode编码由其实际名称引用:UTF7,UTF8和UTF32.因此,Unicode本身就是Little Endian相反的BigEndianUnicode.更新:请参阅最后关于UCS-2和补充字符的部分.

在数据库方面:

SELECT HASHBYTES('MD5', N'è') AS [HashBytesNVARCHAR]
-- FAC02CD988801F0495D35611223782CF
Run Code Online (Sandbox Code Playgroud)

在.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
Run Code Online (Sandbox Code Playgroud)

但是,这个问题属于VARCHAR/ CHARdata,它是ASCII,因此事情有点复杂.

在数据库方面:

SELECT HASHBYTES('MD5', 'è') AS [HashBytesVARCHAR]
-- 785D512BE4316D578E6650613B45E934
Run Code Online (Sandbox Code Playgroud)

我们已经在上面看到了.NET方面.从这些散列值来看,应该有两个问题:

  • 为什么它们中的任何一个都不匹配HASHBYTES
  • 为什么出现"sqlteam.com"的文章中@Eric J.的答案链接显示,他们三个(ASCII,UTF7,和UTF8)所有符合HASHBYTES价值?

有一个答案涵盖了两个问题:代码页.在"sqlteam"文章中进行的测试使用了"安全"的ASCII字符,这些字符在0到127范围内(就int /十进制值而言)在代码页之间没有变化.但是128 - 255范围 - 我们发现"è"字符 - 是扩展集,它确实因代码页而异(这是有意义的,因为这是拥有代码页的原因).

现在尝试:

SELECT HASHBYTES('MD5', 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes]
-- D1457B72C3FB323A2671125AEF3EAB5D
Run Code Online (Sandbox Code Playgroud)

这与ASCII散列值匹配(同样,因为"sqlteam"文章/测试使用了0到127范围内的值,他们在使用时没有看到任何变化COLLATE).好的,现在我们终于找到了匹配VARCHAR/ CHAR数据的方法.都好?

好吧,不是真的.我们来看看我们实际上是在散列什么:

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];
Run Code Online (Sandbox Code Playgroud)

返回:

TheChar TheASCIIvalue   CharCP1255  TheASCIIvalueCP1255
è       232             ?           63
Run Code Online (Sandbox Code Playgroud)

一个??只是为了验证,运行:

SELECT CHAR(63) AS [WhatIs63?];
-- ?
Run Code Online (Sandbox Code Playgroud)

啊,所以Code Page 1255没有这个è角色,所以它被翻译为每个人的最爱?.但是,为什么在使用ASCII编码时,它与.NET中的MD5哈希值相匹配?可能是因为我们实际上并没有匹配哈希值è,而是匹配哈希值?:

SELECT HASHBYTES('MD5', '?') AS [HashBytesVARCHAR]
-- 0xD1457B72C3FB323A2671125AEF3EAB5D
Run Code Online (Sandbox Code Playgroud)

对.真正的ASCII字符集只是前128个字符(值0到127).正如我们刚才看到的那样,è是232.因此,ASCII在.NET中使用编码并没有那么有用.也没有COLLATE在T-SQL方面使用.

是否有可能在.NET端获得更好的编码?是的,通过使用Encoding.GetEncoding(Int32),它允许指定代码页.可以使用以下查询发现要使用的代码页(在使用sys.columns列而不是文字或变量时使用):

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
Run Code Online (Sandbox Code Playgroud)

上面的查询返回(对我来说):

Latin1_General_100_CI_AS_SC    1252
Run Code Online (Sandbox Code Playgroud)

那么,让我们试试Code Page 1252:

System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è')
// 785D512BE4316D578E6650613B45E934
Run Code Online (Sandbox Code Playgroud)

呜啊!我们匹配VARCHAR使用默认SQL Server排序规则的数据:).当然,如果数据来自数据库或字段设置为不同的排序规则,那么GetEncoding(1252) 可能无效,您必须使用上面显示的查询找到实际匹配的代码页(代码页用于多个排序规则,因此不同的校对并不一定意味着不同的代码页.

要查看可能的代码页值以及它们所属的文化/区域设置,请在此处查看代码页列表(列表位于"备注"部分).


有关实际存储在NVARCHAR/ NCHARfields中的内容的其他信息:

可以存储任何UTF-16字符(2或4个字节),但内置函数的默认行为假定所有字符都是UCS-2(每个2字节),这是UTF-16的子集.从SQL Server 2012开始,可以访问一组支持4字节字符(称为补充字符)的Windows排序规则.使用其中一个结束的Windows排序规则(_SC为列指定或直接在查询中)将允许内置函数正确处理4字节字符.

-- 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];
Run Code Online (Sandbox Code Playgroud)

返回:

SupplementaryChar   LEN   DATALENGTH   UNICODE   LEFT   HASHBYTES
                  2     4             55393    ?     0x7A04F43DA81E3150F539C6B99F4B8FA9
                  1     4            165739         0x7A04F43DA81E3150F539C6B99F4B8FA9
Run Code Online (Sandbox Code Playgroud)

如你所见,既不受影响DATALENGTHHASHBYTES不受影响.有关详细信息,请参阅排序规则和Unicode支持的MSDN页面(特别是"补充字符"部分).

  • 我打算关闭这个问题[作为我几个月前回答的一个副本](http://stackoverflow.com/questions/22353446/why-generated-md5-hash-in-sql-server-are-not-equal/22354026?noredirect = 1)但是这个答案比我在其他问题中回答的要好得多,我把这个旧问题作为这个副本:)关闭了:) (2认同)