【问题标题】:Collision rate of two 32-bit hashes vs one 64-bit hash? (uncorrelated?)两个 32 位哈希与一个 64 位哈希的冲突率? (不相关?)
【发布时间】:2018-09-13 13:47:37
【问题描述】:

我见过几个问题,问“两个 16 位散列是否与 32 位散列具有相同的冲突率?”或“两个 32 位散列是否具有与 64 位散列相同的冲突率?”答案似乎是“是的,如果它们是不相关的体面散列函数”。但这意味着什么?

MurmurHash3 的作者这样说:

MurmurHash2_x86_64 并行计算两个 32 位结果并在最后混合它们,这速度很快,但意味着抗碰撞性仅与 32 位散列一样好。我建议避免使用这种变体。

他建议不要使用MurmurHash2_x86_64,但没有提到关于MurmurHash3_x86_128 的此类建议,它似乎将四个 32 位结果混合以产生128 位结果。

而且这个函数看起来更糟:如果消息小于 8 个字节,h3h4 的输出总是会发生冲突。 h2 也容易发生冲突,100% 的时间会产生这样的结果:

种子 = 0,dataArr = {0} h1 = 2294590956, h2 = 1423049145 h3 = 1423049145, h4 = 1423049145 种子 = 0,dataArr = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} h1 = 894685359,h2 = 2425853539,h3 = 2425853539,h4 = 2425853539 另一个例子:“bryc”的哈希 - e87e2554db409442db409442db409442 db409442 重复 3 次

任何长度小于 16 的空字节组合都会导致这些冲突,无论种子如何。

无论如何,如果 Appleby 所说的关于他的功能是真的,两个 32 位结果的抗碰撞性并不比单个 32 位结果好,为什么每次我强制碰撞一个结果,没有失败,其他不受影响?仅在一个哈希中的冲突呈指数级增长。

MurmurHash2_x86_64 中 h1 的碰撞... [2228688450、3117914388] !== [2228688450、2877485180] [957654412、3367924496] !== [957654412、762057742] [1904489323、1019367692] !== [1904489323、1894970953] [2752611220、3095555557] !== [2752611220、2609462765]

我问这个的原因是因为我想在 JavaScript 中实现一个 64 位(或更高)的哈希来进行体面的错误检测。 32 位散列函数还不够好。 GitHub 上目前没有任何可用的解决方案足够快。由于 JavaScript 使用 32 位按位整数,因此只有在 uint32_t 上使用算术的函数在 JS 中是兼容的。许多 32 位函数似乎能够产生更大的输出而不会造成太多的性能损失。

我已经(在 JavaScript 中)实现了 MurmurHash2_x86_64MurmurHash3_x86_128,它们的性能令人印象深刻。我还实现了MurmurHash2_160

所有这些都具有与 32 位哈希相同的抗碰撞性吗?您如何判断结果是否足够相关以成为问题?我希望 64 位输出具有 64 位哈希的强度,160 位输出与 160 位哈希等一样强 - 同时在 32 位算术要求(JavaScript 限制)下

更新:这是我的自定义 64 位哈希,专为速度而设计(比我在 Chrome/Firefox 下优化的 32 位 MurmurHash3 更快)。

function cyb_beta3(key, seed = 0) {
    var m1 = 1540483507, m2 = 3432918353, m3 = 433494437, m4 = 370248451;
    var h1 = seed ^ Math.imul(key.length, m3) + 1;
    var h2 = seed ^ Math.imul(key.length, m1) + 1;

    for (var k, i = 0, chunk = -4 & key.length; i < chunk; i += 4) {
        k = key[i+3] << 24 | key[i+2] << 16 | key[i+1] << 8 | key[i];
        k ^= k >>> 24;
        h1 = Math.imul(h1, m1) ^ k; h1 ^= h2;
        h2 = Math.imul(h2, m3) ^ k; h2 ^= h1;
    }
    switch (3 & key.length) {
        case 3: h1 ^= key[i+2] << 16, h2 ^= key[i+2] << 16;
        case 2: h1 ^= key[i+1] << 8, h2 ^= key[i+1] << 8;
        case 1: h1 ^= key[i], h2 ^= key[i];
                h1 = Math.imul(h1, m2), h2 = Math.imul(h2, m4);
    }
    h1 ^= h2 >>> 18, h1 = Math.imul(h1, m2), h1 ^= h2 >>> 22;
    h2 ^= h1 >>> 15, h2 = Math.imul(h2, m3), h2 ^= h1 >>> 19;

    return [h1 >>> 0, h2 >>> 0];
}

它基于 MurmurHash2。每个内部状态h1h2 分别初始化,但与相同的密钥块混合。然后它们与备用状态(例如h1 ^= h2)混合。作为最终确定的一部分,它们在最后再次混合。

有什么可以表明这比真正的 64 位散列更弱吗?它正确地通过了我自己的基本雪崩/碰撞测试,但我不是专家。

【问题讨论】:

    标签: javascript hash probability hash-collision murmurhash


    【解决方案1】:

    MurmurHash2_x86_64MurmurHash3_x86_128的区别在于前者只做一个 [32-bit 32-bit] -> 64-bit mix,而后者做了128-bit混合每 16 个字节(虽然不是完整的混合,但足以达到此目的)。

    因此,从逻辑上讲,MurmurHash2_x86_64 将输入拆分为 2 个完全分离的流,为每个流计算 32 位哈希,然后将两个 32 位结果混合为一个 64 位结果。所以这不是真正的 64 位哈希。例如,如果一个流损坏,但偶然保留了相同的哈希值,则不会注意到这种损坏。而且这个事件的概率大致相同,就好像你一开始就有一个 32 位哈希一样。所以这个哈希的强度小于 64 位。

    另一方面,MurmurHash3_x86_128 内部有一个 128 位状态,每 16 个输入字节混合(即,所有 16 字节输入几乎立即影响内部状态,而不仅仅是在末尾),所以这是真正的 64 位哈希。

    【讨论】:

    • MurmurHash3_x86_128 不是 128 位散列,MurmurHash2_160 不是 160 位散列吗?所以从你所说的来看,我看到MurmurHash2_x86_64 直到最后都没有混合内部状态h1h2。如果要在类似于MurmurHash3_x86_128 的输入混合阶段通过交替状态的加法或异或来这样做,那么它是否具有适当的 64 位散列的冲突属性?为此,我创建了一个slight modification (see diff),以使用 XOR 而不是加法更接近地匹配其他人。
    • 另外:是否可以进行测试来证明MurmurHash2_x86_64 的 64 位输出与真正的 64 位散列的冲突更多?简单地搜索h1 的冲突以查看h2 是否也曾发生冲突,似乎是无效的。
    • @bryc:“那么它会具有适当的 64 位哈希的冲突属性吗?”:是的,如果你正确地进行混合。但它是黑色艺术,这里的“正确”意味着什么。有哈希函数的测试。但是,例如,如果哈希通过 SMHasher,并不意味着它是完美的。
    • @bryc:我举了一个例子,强度明显低于64位,所以不需要测试来证明。但无论如何,如果你想玩这些东西,你可能想尝试使用更窄的哈希值,比如 8 位,以通过小的计算工作使哈希强度差异更加明显。
    • @bryc:仅供参考:出于性能原因,其他哈希使用相同的想法,例如 XXHash。它使用 4 个并行流,然后混合结果(同样,出于性能原因)。但我认为很难创建一个测试来证明它的强度比 32 位低一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-21
    • 1970-01-01
    • 2018-11-28
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 2018-10-08
    相关资源
    最近更新 更多