【问题标题】:TSQL MD5 generation with UTF8使用 UTF8 生成 TSQL MD5
【发布时间】:2020-01-14 10:37:02
【问题描述】:

我有一个 .NET 函数 MD5,它在“146.185.59.178acu-cell.com”上运行时返回 f36674ed3dbcb151e1c0dfe4acdbb9f5

public static String MD5(String s)
{
    using (var provider = System.Security.Cryptography.MD5.Create())
    {
        StringBuilder builder = new StringBuilder();

        foreach (Byte b in provider.ComputeHash(Encoding.UTF8.GetBytes(s)))
            builder.Append(b.ToString("x2").ToLower());

        return builder.ToString();
    }
}

我在 TSQL 中编写了相同的代码,但由于某种原因,只有 varchar 返回了预期的结果。 nvarchar 返回不同的 md5:f04b83328560f1bd1c08104b83bc30ea

declare @v varchar(150)   = '146.185.59.178acu-cell.com'
declare @nv nvarchar(150) = '146.185.59.178acu-cell.com'


select LOWER(CONVERT(VARCHAR(32), HashBytes('MD5', @v), 2))  
--f36674ed3dbcb151e1c0dfe4acdbb9f5
select LOWER(CONVERT(VARCHAR(32), HashBytes('MD5',@nv), 2)) 
--f04b83328560f1bd1c08104b83bc30ea

不确定这里发生了什么,因为我确实希望 nvarchar 返回 f36674ed3dbcb151e1c0dfe4acdbb9f5 就像在 .NET 中一样

【问题讨论】:

  • 你应该真的停止使用md5;这不是一个安全的哈希。根据HASHBYTES (Transact-SQL)“从 SQL Server 2016 (13.x) 开始,不推荐使用 MD2、MD4、MD5、SHA 和 SHA1 算法。改用 SHA2_256 或 SHA2_512。旧算法将继续工作,但它们会引发弃用事件。”
  • 另外,您为什么希望 varcharnvarchar 的哈希值相同?它们不是相同的数据类型。
  • 但确实如此,@IanKemp。包含相同字符的nvarchar 值和varcharnot 具有相同的值。像'I believe varchar and nvarchar are the same' = N'I believe varchar and nvarchar are the same' 这样的东西会返回true 的唯一原因是varchar 首先隐式 转换为nvarchar。如果您要比较它们的基础(二进制)值,它们将相同。
  • @Larnu 是的,我有一个暂时的脑残 ;)
  • 另请注意,SQL Server(2019 年之前的版本)不支持 UTF-8,因此,如果您包含除普通旧 ASCII 字符之外的任何内容,即使使用 VARCHAR,您也会看到差异 - - 通常的默认 (Latin1_) 排序规则更像 Windows-1252。使用Encoding.Unicode 至少与NVARCHAR 保持一致。

标签: c# .net tsql


【解决方案1】:

您得到不同的哈希值,因为文本的二进制表示不同。以下查询证明了这一点:

declare @v  varchar(150)  = '146.185.59.178acu-cell.com'
declare @nv nvarchar(150) = '146.185.59.178acu-cell.com'

select convert(varbinary(max), @v)  -- 0x3134362E3138352E35392E3137386163752D63656C6C2E636F6D
select convert(varbinary(max), @nv) -- 0x3100340036002E003100380035002E00350039002E003100370038006100630075002D00630065006C006C002E0063006F006D00

nvarchar 的额外 0 字节是由于它是 2 字节 Unicode 数据类型。 Refer to MSDN for more information on Unicode in SQL Server.

【讨论】:

  • 我们知道为什么 SQL 字符串不同,这不是问题所在。问题是为什么 .NET Unicode 字符串不等于 SQL Unicode 字符串。
  • 因为 UTF-8 编码,顾名思义,是 8 位。 MSSQL的nvarchar是UCS-2,即2字节或16位。如果您想在两个平台上获得相同的结果,请在 C# 中使用 Encoding.BigEndianUnicode,它与 UCS-2 非常接近,可以在大多数情况下工作。
  • @user11658885 但您永远不会尝试计算 .NET 字符串的 has。 Windows 和 .NET 一直使用 UTF16LE。您的代码使用 Encoding.UTF8 而不是 Encoding.UTF16
【解决方案2】:

原来我需要将 NVarChar 显式转换为 UTF8

在网上找到了这段代码:

    CREATE FUNCTION [dbo].[fnUTF8] (
    @String NVarChar(max)
) RETURNS VarChar(max) AS BEGIN
    DECLARE  @Result    VarChar(max)
        ,@Counter   Int
        ,@Len       Int
    SELECT   @Result    = ''
        ,@Counter   = 1
        ,@Len       = Len(@String)
    WHILE (@@RowCount > 0)
        SELECT   @Result    = @Result
                    + CASE  WHEN Code < 128     THEN ''
                        WHEN Code < 2048    THEN Char(192 + Code / 64)
                                    ELSE Char(224 + Code / 4096)
                        END
                    + CASE  WHEN Code < 128     THEN Char(Code)
                        WHEN Code < 2048    THEN Char(128 + Code % 64)
                                    ELSE Char(128 + Code / 64 % 64)
                        END
            ,@Counter   = @Counter + 1
        FROM    (SELECT UniCode(SubString(@String,@Counter,1)) AS Code) C
        WHERE   @Counter <= @Len
    RETURN  @Result
END
GO

现在我这样使用它:

select LOWER(CONVERT(VARCHAR(32), HashBytes('MD5', [dbo].[fnUTF8](@nv)), 2))

【讨论】:

  • 这在 SQL Server 2019 中要简单得多:SELECT HASHBYTES('MD5', CONVERT(VARCHAR(MAX), N'146.185.59.178acu-cell.com') COLLATE Latin1_General_100_CI_AS_SC_UTF8)。请注意,像这样的函数可能会产生糟糕的性能,如果您需要将它应用于许多行,这可能是一个问题。在这种情况下,在客户端执行哈希并仅向 SQL Server 提供字节是一个更好的主意(至少因为 SQL Server 将坚持为不再被认为安全的哈希引发弃用事件)。
  • 另请注意,如果您的输入仅包含 ASCII 字符,则使用(比如)Latin1_General_CI_AS 排序规则简单转换为 VARCHAR 就足够了,因为这两种编码都是 ASCII 兼容的。如果你有非 ASCII 字符,你只需要处理 UTF-8。
  • @JeroenMostert 谢谢!不幸的是,我们使用 SQL 2016 (13.x)
  • 为什么坚持使用UTF8而不是UTF16?如果您使用Encoding.Unicode,则无需编写任何此(慢)代码。如果您有使用 UTF8 的正当理由,一个快速替代方法是使用与客户端相同的代码在 C# 中编写 SQLCLR UDF。
  • @PanagiotisKanavos 因为在使用 UTF8 的服务器上使用 .NET 编码了数亿条旧记录,如 OP 中所述。因此,要使其发挥作用,我们需要使自己与现有数据保持一致......
猜你喜欢
  • 2017-11-08
  • 1970-01-01
  • 2020-02-04
  • 1970-01-01
  • 2011-07-26
  • 2015-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多