【问题标题】:SQL CLR function based on .net ComputeHash is not working with Cyrrilic基于 .net ComputeHash 的 SQL CLR 函数不适用于 Cyrillic
【发布时间】:2016-05-17 22:56:32
【问题描述】:

我编写了以下SQL CLR 函数以散列大于8000 字节的字符串值(T-SQL 内置HASHBYTES 函数的输入值限制):

[SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)]
public static SqlBinary HashBytes(SqlString algorithm, SqlString value)
{
    HashAlgorithm algorithmType = HashAlgorithm.Create(algorithm.Value);

    if (algorithmType == null || value.IsNull)
    {
        return new SqlBinary();
    }
    else
    {
        byte[] bytes = Encoding.UTF8.GetBytes(value.Value);
        return new SqlBinary(algorithmType.ComputeHash(bytes));
    }
}

它适用于拉丁字符串。例如,以下哈希是相同的:

SELECT dbo.fn_Utils_GetHashBytes ('MD5', 'test'); -- 0x098F6BCD4621D373CADE4E832627B4F6
SELECT HASHBYTES ('MD5', 'test');                 -- 0x098F6BCD4621D373CADE4E832627B4F6

问题在于它不适用于西里尔字符串。例如:

SELECT dbo.fn_Utils_GetHashBytes ('MD5 ', N'даровете на влъхвите') -- NULL
SELECT HashBytes ('MD5 ',N'даровете на влъхвите') -- 0x838B1B625A6074B2BE55CDB7FCEA2832

SELECT dbo.fn_Utils_GetHashBytes ('SHA256', N'даровете на влъхвите') -- 0xA1D65374A0B954F8291E00BC3DD9DF655D8A4A6BF127CFB15BBE794D2A098844
SELECT HashBytes ('SHA2_256',N'даровете на влъхвите') -- 0x375F6993E0ECE1864336E565C8E14848F2A4BAFCF60BC0C8F5636101DD15B25A 

我得到了NULLMD5,尽管如果代码作为控制台应用程序执行,它会返回值。谁能告诉我我做错了什么?


另外,我从here 得到了这个函数,其中一个 cmets 说:

小心 CLR SP 参数被静默截断为 8000 字节 - 我必须用 [SqlFacet(MaxSize = -1)] 标记参数,否则第 8000 个之后的字节将被忽略!

但我已经对此进行了测试,并且工作正常。例如,如果我生成一个 8000 字节字符串的散列和相同字符串加一个符号的第二个散列,我得到的散列是不同的。

DECLARE @A VARCHAR(MAX) = '8000 bytes string...'
DECLARE @B VARCHAR(MAX) = @A + '1'
SELECT LEN(@A), LEN(@B)

SELECT IIF(dbo.fn_Utils_GetHashBytes ('MD5', @A + '1') = dbo.fn_Utils_GetHashBytes ('MD5', @B), 1, 0) -- 0

我应该担心这个吗?

【问题讨论】:

  • 关于 NULL - 你在 MD5 字符串后面有一个额外的空格,所以没有创建哈希算法。删除应该可以解决它。
  • 关于不同的哈希值:内置 HashBytes 在您的情况下对 unicode 字符串(即 utf-16)进行操作。另一方面,您的函数在计算哈希之前接受 utf-16 输入并将其转换为 utf-8。输入不同,哈希值也不同。
  • 不。在拉丁文情况下,这两个函数都采用 ASCII 输入(没有 N 前缀),对于 ASCII 字符串,utf-8 表示是相同的,因此哈希值相等。

标签: c# .net tsql hash sqlclr


【解决方案1】:
 Encoding.UTF8.GetBytes(...)

SQL Server 没有 UTF-8 的概念。使用 UCS-2 (UTF-16) 或 ASCII。使用的编码必须与您传递给HASHBYTES 的编码相匹配。您可以很容易地看到 HASHBYTES 的哈希值与 VARCHARNVARCHAR 不同:

select HASHBYTES('MD5', 'Foo')  -- 0x1356C67D7AD1638D816BFB822DD2C25D
select HASHBYTES('MD5', N'Foo') -- 0xB25FF0AD90D09D395090E8A29FF4C63C

最好将 SQLCLR 函数更改为接受字节,而不是字符串,并在调用者中处理转换为 VARBINARY

 SELECT dbo.fn_Utils_GetHashBytes ('MD5', CAST(N'даровете на влъхвите' AS VARBINARY(MAX));

仅供参考 SQL Server 2016 已取消对 HASHBYTES 的 8000 字节限制:

对于 SQL Server 2014 及更早版本,允许的输入值限制为 8000 字节。

【讨论】:

    【解决方案2】:

    如需详细说明您看到差异的原因,请参阅我对以下问题的回答:

    TSQL md5 hash different to C# .NET md5

    对于不希望自己编译和部署它的任何人,此功能可在SQL# SQLCLR 函数、存储过程等库的免费版本中使用(我是该库的创建者,但 Util_HashUtil_HashBinary 等等都是免费的)。 SQL# 中的两个 Util_Hash* 函数在问题中显示的内容之间存在一个区别:问题中显示的函数采用 NVARCHAR / SqlString 输入参数,而 SQL# 函数采用VARBINARY / SqlBinary 输入。区别在于:

    • 接受VARBINARY 输入也适用于二进制源数据(文件、图像、加密值等)
    • 虽然接受VARBINARY 输入确实需要在函数调用中执行CONVERT(VARBINARY(MAX), source_string) 的额外步骤,但这样做会保留用于VARCHAR 数据的任何代码页。虽然不经常使用,但在处理非 Unicode 数据时会很方便。

    关于其他帖子的警告:

    小心 CLR SP 参数被静默截断为 8000 个字节 - 我必须使用 [SqlFacet(MaxSize = -1)] 标记参数,否则第 8000 个字节之后的字节将被忽略!

    但您没有遇到同样的事情:这是由于 SSDT 为 SQLCLR 对象生成 T-SQL 包装器对象的方式发生了变化。在早期版本中(尤其是 VS 2013 之前的 Visual Studio 版本),默认行为是使用 NVARCHAR(MAX) 表示 SqlChars,使用 NVARCHAR(4000) 表示 SqlString。但是在某些时候(我不想说 VS 2013,因为 Visual Studio 和 SSDT 是独立产品,即使 VS 附带 SSDT)默认更改为使用 NVARCHAR(MAX) SqlCharsSqlString .发布警告的人(2013 年 2 月 6 日)一定使用的是 SSDT 的早期版本。尽管如此,明确并使用[SqlFacet(MaxSize = -1)] 并没有什么坏处(甚至是一个好习惯)。

    关于if (algorithmType == null || value.IsNull) 逻辑:由于NULL 中的任何一个都应该返回NULL,因此最好删除该逻辑并使用CREATE FUNCTION 语句的WITH RETURNS NULL ON NULL INPUT 选项。然而,不幸的是,任何 SSDT 构造都不支持此选项(即没有SqlFacet)。因此,为了启用此选项,您可以创建一个部署后 SQL 脚本(它将在主脚本之后自动部署),它发出一个带有所需定义的 ALTER FUNCTION。投票支持我的 Connect 建议以原生支持此选项不会有什么坏处:Implement OnNullCall property in SqlFunctionAttribute for RETURNS NULL ON NULL INPUT SQLCLR。在实际层面上,性能提升主要体现在您为 @value 参数传递较大值但不知何故 @algorithmNULL 的情况下,因此您最终不会使用 @ 的值987654351@。使用RETURNS NULL ON NULL INPUT 选项的原因是,当您调用传入SqlStringSqlBinary 的SQLCLR 函数时,整个值将被复制到应用程序域的内存中。也就是说,如果您提前知道您不会使用它,您就不需要浪费时间、内存和 CPU :-)。即使传入较小的值,您也可能会看到对经常调用非常的函数有好处。


    关于警告和您的测试的旁注:SQLCLR 不支持VARCHAR,仅支持NVARCHAR。因此,从来没有 8000 个字符的限制,因为如果 SSDT 没有自动使用 NVARCHAR(MAX),那么该限制将是 4000 个字符。因此,如果存在差异,那么首先会看到仅使用 4000 和 4001 个字符进行测试。


    更新:从 SQL Server 2019 开始,现在可以use UTF-8 natively via the _UTF8 collations。但是,您仍然不能将 UTF-8 字符 字符串传递到 SQLCLR 对象,因为 SQLCLR API 只处理 NVARCHAR 而不是 VARCHAR。因此,尝试传入'UTF-8 encoded string' 仍然会以UTF-16 LE 的形式通过,因为它将在传入的过程中被隐式转换。将UTF-8 编码字符转换为SQLCLR 的唯一方法是首先将它们转换为VARBINARY并将这些字节传递给 SQLCLR 对象(如 VARBINARY -> SqlBinary / SqlBytes)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-30
      • 1970-01-01
      • 2023-03-10
      • 2017-03-26
      • 1970-01-01
      • 1970-01-01
      • 2018-09-08
      • 1970-01-01
      相关资源
      最近更新 更多