TSQL md5 哈希不同于 C# .NET md5
TSQL md5 hash different to C# .NET md5
我生成了一个 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,关于我做错了什么有什么想法吗?
SQL 服务器使用 UCS-2 而不是 UTF-8 来编码字符数据。
如果您使用的是 NVarChar 字段,则以下方法可行:
System.Text.Encoding.Unicode.GetBytes("è"); // Updated per @srutzky's comments
有关 SQL 和 C# 散列的更多信息,请参阅
如果您正在处理 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" 文章显示其中三个(
ASCII
、UTF7
和 UTF8
)都匹配HASHBYTES
值?
有一个答案涵盖了这两个问题:代码页。在 "sqlteam" 文章中完成的测试使用了 "safe" ASCII 字符,这些字符在 0 - 127 范围内(根据 int / decimal 值),代码页之间没有变化。但是 128 - 255 范围——我们找到“è”字符的地方——是 Extended 集,它确实因代码页而异(这是有道理的,因为这是有代码的原因页数)。
现在试试:
SELECT HASHBYTES('MD5', 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes]
-- D1457B72C3FB323A2671125AEF3EAB5D
与 ASCII
散列值相匹配(同样,因为 "sqlteam" 文章/测试使用了 0 - 127 范围内的值,所以在使用 [=39= 时他们没有看到任何变化]).太好了,现在我们终于找到了匹配 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];
Returns:
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
上面的查询returns(对我来说):
Latin1_General_100_CI_AS_SC 1252
所以,让我们试试代码页 1252:
System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è')
// 785D512BE4316D578E6650613B45E934
呜呜呜!我们有一个 VARCHAR
数据的匹配项,它使用我们默认的 SQL 服务器排序规则 :)。当然,如果数据来自数据库或字段设置为不同的排序规则,那么 GetEncoding(1252)
可能 不起作用,您将不得不使用以下命令找到实际匹配的代码页上面显示的查询(代码页用于许多归类,因此不同的归类 不一定 暗示不同的代码页)。
要查看可能的代码页值是什么,以及它们所属的文化/区域设置,请参阅代码页列表 here(列表位于 "Remarks" 部分)。
与 NVARCHAR
/ NCHAR
字段中实际存储内容相关的附加信息:
可以存储任何 UTF-16 字符(2 或 4 个字节),尽管内置函数的默认行为假定所有字符都是 UCS-2(每个 2 个字节),这是一个子集UTF-16 的。从 SQL Server 2012 开始,可以访问一组 Windows 支持称为补充字符的 4 字节字符的排序规则。使用这些以 _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];
Returns:
SupplementaryChar LEN DATALENGTH UNICODE LEFT HASHBYTES
2 4 55393 � 0x7A04F43DA81E3150F539C6B99F4B8FA9
1 4 165739 0x7A04F43DA81E3150F539C6B99F4B8FA9
如您所见,DATALENGTH
和 HASHBYTES
均未受到影响。有关详细信息,请参阅 Collation and Unicode Support 的 MSDN 页面(特别是 "Supplementary Characters" 部分)。
我遇到了同样的问题,正如@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
sql 服务器 hashbytes 总是像 System.Text.Encoding.Unicode
在像阿拉伯波斯语这样的unicode字符上,...
如果你使用 Utf8.Unicode 或 Ascii.Unicode 你会看到差异
如果您使用 Utf8.Unicode,sql 服务器和 c# 的 return 结果将相同
我生成了一个 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,关于我做错了什么有什么想法吗?
SQL 服务器使用 UCS-2 而不是 UTF-8 来编码字符数据。
如果您使用的是 NVarChar 字段,则以下方法可行:
System.Text.Encoding.Unicode.GetBytes("è"); // Updated per @srutzky's comments
有关 SQL 和 C# 散列的更多信息,请参阅
如果您正在处理 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" 文章显示其中三个(
ASCII
、UTF7
和UTF8
)都匹配HASHBYTES
值?
有一个答案涵盖了这两个问题:代码页。在 "sqlteam" 文章中完成的测试使用了 "safe" ASCII 字符,这些字符在 0 - 127 范围内(根据 int / decimal 值),代码页之间没有变化。但是 128 - 255 范围——我们找到“è”字符的地方——是 Extended 集,它确实因代码页而异(这是有道理的,因为这是有代码的原因页数)。
现在试试:
SELECT HASHBYTES('MD5', 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes]
-- D1457B72C3FB323A2671125AEF3EAB5D
与 ASCII
散列值相匹配(同样,因为 "sqlteam" 文章/测试使用了 0 - 127 范围内的值,所以在使用 [=39= 时他们没有看到任何变化]).太好了,现在我们终于找到了匹配 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];
Returns:
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
上面的查询returns(对我来说):
Latin1_General_100_CI_AS_SC 1252
所以,让我们试试代码页 1252:
System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è')
// 785D512BE4316D578E6650613B45E934
呜呜呜!我们有一个 VARCHAR
数据的匹配项,它使用我们默认的 SQL 服务器排序规则 :)。当然,如果数据来自数据库或字段设置为不同的排序规则,那么 GetEncoding(1252)
可能 不起作用,您将不得不使用以下命令找到实际匹配的代码页上面显示的查询(代码页用于许多归类,因此不同的归类 不一定 暗示不同的代码页)。
要查看可能的代码页值是什么,以及它们所属的文化/区域设置,请参阅代码页列表 here(列表位于 "Remarks" 部分)。
与 NVARCHAR
/ NCHAR
字段中实际存储内容相关的附加信息:
可以存储任何 UTF-16 字符(2 或 4 个字节),尽管内置函数的默认行为假定所有字符都是 UCS-2(每个 2 个字节),这是一个子集UTF-16 的。从 SQL Server 2012 开始,可以访问一组 Windows 支持称为补充字符的 4 字节字符的排序规则。使用这些以 _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];
Returns:
SupplementaryChar LEN DATALENGTH UNICODE LEFT HASHBYTES
2 4 55393 � 0x7A04F43DA81E3150F539C6B99F4B8FA9
1 4 165739 0x7A04F43DA81E3150F539C6B99F4B8FA9
如您所见,DATALENGTH
和 HASHBYTES
均未受到影响。有关详细信息,请参阅 Collation and Unicode Support 的 MSDN 页面(特别是 "Supplementary Characters" 部分)。
我遇到了同样的问题,正如@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
sql 服务器 hashbytes 总是像 System.Text.Encoding.Unicode 在像阿拉伯波斯语这样的unicode字符上,... 如果你使用 Utf8.Unicode 或 Ascii.Unicode 你会看到差异 如果您使用 Utf8.Unicode,sql 服务器和 c# 的 return 结果将相同