2

我正在尝试匹配 SQL Server 和 Snowflake 中表中某些数据的 sha1 值。

我通过以下方式在 SQL 服务器中获得了拉丁字符的 sha1 -

select  sys.fn_varbintohexsubstring(0, HASHBYTES('SHA1',cast('á'  as varchar(1))),1,0) 

这将返回 b753d636f6ee46bb9242d01ff8b61f715e9a88c3

Snowflake 中的 sha1 函数为同一个字符返回不同的值。

select sha1(cast('á' as varchar))
Result - 2b9cc8d86a48fd3e4e76e117b1bd08884ec9691d

注意 - SQL Server 中的数据类型是 nvarchar,而 Snowflake 中的数据类型是带有默认排序规则的 varchar。对于英文字符,将 nvarchar 转换为 varchar 后 sha1 值匹配。但是,拉丁字符并非如此。

有没有办法匹配非英语字符的 sha1 值?我需要在 SQL Server 2017 及更低版本中获取值 '2b9cc8d86a48fd3e4e76e117b1bd08884ec9691d',因为它是 Oracle、Snowflake 和 Hive 等其他数据库返回的值。

谢谢

4

2 回答 2

4

TL;DR:varchar计算哈希时切勿使用。在此过程中,您可以踩到的耙子太多了。

举个例子,我调整了您的代码以便于理解,并在具有Latin1_General_100_CI_AS默认排序规则的数据库的上下文中运行它:

declare @a nchar(1) = N'á';
declare @b char(1) = cast(@a as char(1));

select @b as [Char], ascii(@b) as [A], unicode(@b) as [U], HASHBYTES('SHA1',@b) as [Hash]
union all
select @a, ascii(@a), unicode(@a), HASHBYTES('SHA1',@a);

结果是:

Char    A    U Hash
---- ---- ---- ------------------------------------------
á     225  225 0xB753D636F6EE46BB9242D01FF8B61F715E9A88C3
á     225  225 0xA4BCF633D5ECCD3F2A55CD0AD3D109A108A45F02

但是,如果我将数据库上下文更改为另一个数据库,使用Cyrillic_General_100_CI_AS排序规则,相同的代码会突然返回不同的值:

Char    A    U Hash
---- ---- ---- ------------------------------------------
a      97   97 0x86F7E437FAA5A7FCE15D1DDCB9EAEAEA377667B8
á      97  225 0xA4BCF633D5ECCD3F2A55CD0AD3D109A108A45F02

如您所见,第一行中的 [Char] 现在是一个不同的字符(小拉丁文“а”)。除非您的数据是 Unicode 或二进制形式,否则无法阻止这种隐式代码页调整。


您的选择

  1. 升级到 MS SQL Server 2019,或迁移到 Azure SQL 数据库。从这个版本开始,您实际上可以以 UTF-8 编码存储字符串,尽管您可能会因此受到性能影响(它是否会引起注意,取决于您的使用模式)。
  2. 在外部计算哈希(意思是,不在 SQL 中)。您可以在 C# 中编写 CLR 函数,或者在 Java 中编写类似的函数(请参阅 Elliott Brossard 的回答)。这将增加您的解决方案的复杂性,例如,您公司的政策可能不允许将外部代码放入您的数据库中。另外,维护外部组件通常很麻烦。
于 2021-08-30T15:00:23.040 回答
0

您可以使用 Java UDF 计算 Latin-1 字符串的 SHA1 哈希值。这是一个例子:

create function latin1sha1(str varchar)
returns varbinary language java handler = 'Latin1Sha1.compute' as $$
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

class Latin1Sha1 {
  public byte[] compute(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
    MessageDigest hash = MessageDigest.getInstance("SHA-1");
    hash.update(str.getBytes("ISO-8859-1"));  // AKA Latin-1
    return hash.digest();
  }
}
$$;

select hex_encode(latin1sha1('á'));

这返回B753D636F6EE46BB9242D01FF8B61F715E9A88C3

于 2021-08-31T16:51:48.273 回答