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

标签 .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,关于我做错了什么想法?

最佳答案

如果要处理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)都与HASHBYTES值匹配?


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

现在尝试:

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


该值与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];


返回值:

TheChar TheASCIIvalue   CharCP1255  TheASCIIvalueCP1255
è       232             ?           63


?吗?只是为了验证,运行:

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


嗯,所以代码页1255没有è字符,因此它被转换为每个人都喜欢的?。但是,为什么在使用ASCII编码时,它与.NET中的MD5哈希值匹配?可能是我们实际上没有匹配è的哈希值,而是匹配了?的哈希值:

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


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

是否可以在.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


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

Latin1_General_100_CI_AS_SC    1252


因此,让我们尝试一下代码页1252:

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


呜呜!我们使用默认的SQL Server排序规则对VARCHAR数据进行匹配:)。当然,如果数据来自数据库或设置为其他排序规则的字段,则GetEncoding(1252)可能无法正常工作,您将必须使用上面显示的查询找到实际匹配的代码页(在许多情况下都使用代码页归类,因此不同的归类并不一定意味着不同的代码页。

若要查看可能的代码页值,以及它们对应的文化/地区,请查看代码页here的列表(列表在“备注”部分中)。



NVARCHAR / NCHAR字段中实际存储的内容有关的其他信息:

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


返回值:

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


如您所见,DATALENGTHHASHBYTES均不受影响。有关更多信息,请参见Collation and Unicode Support的MSDN页面(特别是“补充字符”部分)。

关于.net - TSQL md5哈希与C#.NET md5不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27908449/

相关文章:

c# - 在 .NET Framework 3.0 中将本地时间转换为 UTC

python /pypyODBC : Row Insert Using String and NULLs

sql-server - SQL Server 2008 中的 bigint 算术溢出

c# - Entity Framework - 代码优先方法

sql-server - SQL Server : Index columns used in like?

sql - 如何跨多个数据库向sql server中的列添加外键约束?

sql - 获得有条件的连续天数

.net - 带有 .NET 3.5 的 Microsoft Server 2003

c# - WriteLine 类

c# - 需要帮助来决定什么是跟踪系统 (.NET) 的最佳解决方案