【问题标题】:What is the default hash code that Mathematica uses?Mathematica 使用的默认哈希码是什么?
【发布时间】:2011-05-01 16:07:32
【问题描述】:

在线文档说

Hash[expr] 
  gives an integer hash code for the expression expr.
Hash[expr,"type"]
  gives an integer hash code of the specified type for expr.

它还给出了“可能的哈希码类型”:

  • "Adler32" Adler 32 位循环冗余校验
  • “CRC32”32 位循环冗余校验
  • “MD2”128 位 MD2 代码
  • “MD5”128 位 MD5 代码
  • “SHA”160 位 SHA-1 代码
  • “SHA256”256 位 SHA 代码
  • “SHA384”384 位 SHA 代码
  • “SHA512”512 位 SHA 代码

然而,这些都不符合Hash[expr] 返回的默认值。

所以我的问题是:

  • 默认Hash使用什么方法?
  • 还有其他内置的哈希码吗?

【问题讨论】:

标签: hash wolfram-mathematica


【解决方案1】:

我一直在对 Mathematica 10.4 的 32 位和 64 位 Windows 版本进行一些逆向工程,这就是我发现的:

32 位

它使用Fowler–Noll–Vo hash function(FNV-1,之前有乘法),16777619 作为 FNV 素数,84696351 作为偏移基础。此函数应用于表达式数据地址的Murmur3-32 散列值(MMA 使用指针来保存每个数据的一个实例)。地址最终被解析为值 - 对于简单的机器整数,该值是立即的,对于其他人来说有点棘手。 Murmur3-32 实现函数实际上包含一个附加参数(默认为 4,特殊情况下的行为与 Wikipedia 中一样),它选择从输入中的表达式结构中选择多少位。由于普通表达式在内部表示为指针数组,因此可以通过重复将 4(字节 = 32 位)添加到表达式的基指针来获取第一个、第二个等。因此,将 8 传递给函数将给出第二个指针,12 将给出第三个,依此类推。由于内部结构(大整数、机器整数、机器实数、大实数等)具有不同的成员变量(例如,机器整数只有一个指向 int 的指针,一个复数的 2 个指向数字的指针等),对于每个表达式结构有一个“包装器”将其内部成员组合在一个 32 位散列中(基本上是 FNV-1 轮)。最简单的哈希表达式是一个整数。

murmur3_32() 函数的种子为 1131470165,n=0 和其他参数与维基百科中的一样。

所以我们有:

  hash_of_number = 16777619 * (84696351‬ ^ murmur3_32( &number ))

用“^”表示异或。 我真的没有尝试过——指针是使用WINAPI EncodePointer() 编码的,所以它们不能在运行时被利用。 (可能值得在带有 EncodePonter 修改版本的 Wine 下的 Linux 中运行?)


64 位

它使用 FNV-1 64 位散列函数,0xAF63BD4C8601B7DF 作为偏移基础,0x100000001B3 作为 FNV 素数,以及 SIP64-24 散列(here 的参考代码)和前 64 位 0x0AE3F68FE7126BBF21F98EF7F39DE1 作为 k最后 64 位为 k1。该函数应用于表达式的基指针并在内部解析。与 32 位的 murmur3 一样,还有一个附加参数(默认为 8)来选择从输入表达式结构中选择多少个指针。对于每种表达式类型,都有一个包装器通过 FNV-1 64 位循环将结构成员压缩为单个哈希。

对于机器整数,我们有:

    hash_number_64bit = 0x100000001B3 * (0xAF63BD4C8601B7DF ^ SIP64_24( &number ))

再一次,我并没有真正尝试过。有人可以试试吗?


不适合胆小的人

如果您查看他们的notes on internal implementation,他们会说“每个表达式都包含一种特殊形式的哈希码,用于模式匹配和评估。”

他们所指的哈希码是由这些函数生成的——在普通表达式包装函数的某个时刻,有一个赋值将计算出的哈希放入表达式结构本身。

了解他们如何将这些哈希用于模式匹配目的当然很酷。所以我尝试运行 bigInteger 包装器,看看会发生什么——这是最简单的复合表达式。 它开始检查返回 1 的东西——不知道是什么。 所以它执行

    var1 = 16777619 * (67918732 ^ hashMachineInteger(1));

with hashMachineInteger() 就是我们之前所说的——包括值。

然后它从结构 (bignum_length) 中读取 bigInt 的字节长度并运行

    result = 16777619 * (v10 ^ murmur3_32(v6, 4 * v4));

请注意,如果 4 * bignum_length 大于 8,则调用 murmur3_32()(可能与机器整数 $MaxMachineNumber 2^32^32 的最大值有关,并且与 bigInt 应该是什么相反)。

所以,最终的代码是

    if (bignum_length > 8){

    result = 16777619 * (16777619 * (67918732 ^ ( 16777619 * (84696351‬ ^ murmur3_32( 1, 4 )))) ^ murmur3_32( &bignum, 4 * bignum_length ));
    }

我对这种结构的性质提出了一些假设。许多 XOR 的存在以及 16777619 + 67918732 = 84696351‬ 的事实可能会让人们认为某种循环结构被用来检查模式 - 即减去偏移量并除以素数,或类似的东西。 Cassandra 软件使用 Murmur 哈希算法生成令牌 - 请参阅 these images 了解我对“循环结构”的含义。也许每个表达式都使用了各种素数 - 仍然必须检查。


希望对你有帮助

【讨论】:

    【解决方案2】:

    貌似Hash调用了内部Data`HashCode函数,然后除以2,取N[..]的前20位,然后是IntegerPart,加一,即:

        IntegerPart[N[Data`HashCode[expr]/2, 20]] + 1
    

    【讨论】:

    • 有趣的观察,但我认为不太正确:Tally[(IntegerPart[N[Data`HashCode[#]/2, 20]] + 1) - Hash[#] & /@ RandomReal[{-1, 1}, 1000]] 给出了{{0, 256}, {2, 273}, {1, 471}}
    • 这似乎有效:myhash[exp_] := ( (# + Mod[#, 2] - 4 Floor[(Mod[#, 4] + 1)/4]) &@ Data`HashCode[exp])/2
    【解决方案3】:

    默认哈希算法或多或少是一个基本的 32 位哈希函数,应用于底层表达式表示,但确切的代码是 Mathematica 内核的专有组件。它会在 Mathematica 版本之间(并且已经)发生变化,并且缺少许多理想的加密属性,因此我个人建议您将 MD5 或其中一种 SHA 变体用于安全性很重要的任何严肃应用程序。内置哈希旨在用于典型的数据结构使用(例如在哈希表中)。

    您从文档中列出的命名哈希算法是目前唯一可用的。您是否特别在寻找不同的?

    【讨论】:

    • @Michael 你知道是否有办法“内省” Mathematica 来获取此类信息?还有:为什么 Hash 函数的“类型”参数不作为“选项” (->) 实现?嗯,也许我应该发布一个问题......
    • 另外,@Simon,请记住大多数哈希函数(包括 Hash[]、MD5 和 SHA)都不是“完美的”(即单射的),不幸的是,尤其是 Hash。评估并考虑这一点:Length[DeleteDuplicates[Hash /@ Range[10^6]]] =)
    • @Simon But Length@DictionaryLookup[___] - Length@DeleteDuplicates@(Hash /@ DictionaryLookup[___]) == 1 :),所以我认为你应该为你的应用测试单射属性
    • @Simon 所以通过知道:D 来推导出算法
    • 公平地说,我不希望一个名为“Hash”的函数在默认情况下一定是加密安全的。有大量不需要这些属性的哈希用例。
    猜你喜欢
    • 1970-01-01
    • 2010-11-11
    • 2013-10-25
    • 1970-01-01
    • 2015-10-06
    • 2011-08-18
    • 2015-02-10
    • 2013-03-24
    相关资源
    最近更新 更多