我已经生成了一个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,任何关于我做错的想法?
答案 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
值?ASCII
,UTF7
和UTF8
)都匹配{ {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
可能不起作用,您必须使用显示的查询找到实际匹配的代码页上面(代码页用于多个Collations,因此不同的Collation不会必然意味着不同的代码页)。
要查看可能的代码页值以及它们所属的文化/区域设置,请参阅代码页列表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#散列的更多信息,请参阅
答案 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#的返回结果将是相同的