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

时间:2015-01-12 18:29:19

标签: .net sql-server tsql unicode encoding

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

DECLARE @varchar varchar(400) 

SET @varchar = 'è'

SELECT CONVERT(VARCHAR(2000), HASHBYTES( 'MD5', @varchar ), 2)

哪个输出:

785D512BE4316D578E6650613B45E934

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

System.Text.Encoding.UTF8.GetBytes("è")

产生

0a35e149dbbb2d10d744bf675c7744b1

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

4 个答案:

答案 0 :(得分:31)

如果您正在处理NVARCHAR / NCHAR数据(存储为 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

在.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

但是,这个问题与VARCHAR / CHAR数据有关,这是ASCII,所以事情有点复杂。

在数据库方面:

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

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

  • 为什么中的任何都不匹配HASHBYTES值?
  • 为什么在@Eric J.的答案中链接的“sqlteam.com”文章显示其中三个(ASCIIUTF7UTF8)都匹配{ {1}}值?

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

现在尝试:

HASHBYTES

匹配SELECT HASHBYTES('MD5', 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes] -- D1457B72C3FB323A2671125AEF3EAB5D 哈希值(并且再次,因为“sqlteam”文章/测试使用0 - 127范围内的值,他们在使用ASCII时没有看到任何更改)。很好,现在我们终于找到了匹配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];

A TheChar TheASCIIvalue CharCP1255 TheASCIIvalueCP1255 è 232 ? 63 ?只是为了验证,运行:

?

啊,所以代码页1255没有SELECT CHAR(63) AS [WhatIs63?]; -- ? 字符,所以它被翻译为每个人最喜欢的è。但是,为什么在使用ASCII编码时,它与.NET中的MD5哈希值相匹配?可能是因为我们实际上没有匹配?的散列值,而是匹配散列值è

?

烨。真正的ASCII字符集只是前128个字符(值0 - 127)。正如我们刚才看到的那样,SELECT HASHBYTES('MD5', '?') AS [HashBytesVARCHAR] -- 0xD1457B72C3FB323A2671125AEF3EAB5D 是232.因此,在.NET中使用è编码并没有那么有用。也没有在T-SQL端使用ASCII

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

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

所以,让我们试试Code Page 1252:

Latin1_General_100_CI_AS_SC    1252
哇哇!我们匹配使用我们的默认SQL Server排序规则的System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è') // 785D512BE4316D578E6650613B45E934 数据:)。当然,如果数据来自数据库或字段设置为不同的排序规则,那么VARCHAR 可能不起作用,您必须使用显示的查询找到实际匹配的代码页上面(代码页用于多个Collat​​ions,因此不同的Collat​​ion不会必然意味着不同的代码页)。

要查看可能的代码页值以及它们所属的文化/区域设置,请参阅代码页列表here(列表位于“备注”部分)。


GetEncoding(1252) / NVARCHAR字段中实际存储内容相关的其他信息:

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

_SC

返回:

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

如您所见,SupplementaryChar LEN DATALENGTH UNICODE LEFT HASHBYTES 2 4 55393 � 0x7A04F43DA81E3150F539C6B99F4B8FA9 1 4 165739 0x7A04F43DA81E3150F539C6B99F4B8FA9 DATALENGTH都不受影响。有关详细信息,请参阅Collation and Unicode Support的MSDN页面(特别是“补充字符”部分)。

答案 1 :(得分:3)

SQL Server使用UCS-2而不是UTF-8来编码字符数据。

如果您使用的是NVarChar字段,则可以使用以下内容:

System.Text.Encoding.Unicode.GetBytes("è"); // Updated per @srutzky's comments

有关SQL和C#散列的更多信息,请参阅

http://weblogs.sqlteam.com/mladenp/archive/2009/04/28/Comparing-SQL-Server-HASHBYTES-function-and-.Net-hashing.aspx

答案 2 :(得分:0)

我遇到了同样的问题,正如@srutzky评论的那样,可能会发生的事情是我在查询之前没有使用大写字母N,而我得到的是8位扩展ASCII(VARCHAR /字符串没有前缀大写字母N)而不是16位UTF-16 Little Endian(NVARCHAR /字符串前缀为大写字母N)

{Id, UserName, PasswordString, PasswordHashed}

如果你这样做:

SELECT TOP 1 CONVERT(char(32),HashBytes('MD5', 'abc123'),2) FROM [Users]

它会输出: E99A18C428CB38D5F260853678922E03

但如果你这样做,请使用相同的密码(' abc123'):

SELECT CONVERT(char(32),HashBytes('MD5', [PasswordString]),2) FROM [Users]

它会输出: 6E9B3A7620AAF77F362775150977EEB8

我应该做的是:

SELECT CONVERT(char(32),HashBytes('MD5', N'abc123'),2) FROM [Users]

输出相同的结果:6E9B3A7620AAF77F362775150977EEB8

答案 3 :(得分:0)

sql server hashbytes总是像System.Text.Encoding.Unicode一样工作 关于像阿拉伯语波斯语这样的unicode字符,...... 如果您使用Utf8.Unicode或Ascii.Unicode您将看到差异 如果你使用Utf8.Unicode,sql server和c#的返回结果将是相同的