【问题标题】:Best algorithm for hashing number values?散列数值的最佳算法?
【发布时间】:2010-11-24 11:37:23
【问题描述】:

在处理一系列数字时,出于安全原因想要使用哈希结果,从给定的数字系列生成哈希值的最佳方法是什么?输入的示例是信用卡号或银行帐号。首选输出将是单个无符号整数,以帮助进行匹配。

我的感觉是,大多数字符串实现在针对如此短范围的字符运行时似乎具有低熵,因此,冲突率可能比针对更大样本运行时更高。

目标语言是 Delphi,但欢迎其他语言的答案,如果它们可以提供可以导致最佳解决方案的数学基础。

此例程的目的是确定先前收到的卡/帐户是否已被处理。输入文件可能有多个记录,而不是多条记录的数据库,因此性能是一个因素。

【问题讨论】:

  • 重读你的问题......你想用哈希做什么?您提到了安全原因,但问题的其余部分听起来有点像哈希表查找。还是您想执行输入验证 - 一种校验和?
  • 我不确定单个整数是否会为此用途提供足够的空间。对于 16 位信用卡号码,您将遇到冲突 (10^16 >> 2^32)。发生碰撞时你会怎么做?这是一个将在其他地方处理的软故障,否定了这可能提供的性能改进?
  • 最佳测量值是多少?计算速度?内存占用?攻击难?便携性?
  • 出于我的目的,我很可能还会结合其他信息来帮助识别不当碰撞。我想做的是找到最佳算法来获得相当均匀的散列值分布。由于甚至没有考虑 alpha,我想知道是否有更好的方法来散列数字。
  • @Rob,计算速度。由于丢失了有效数字,因此如果解析为整数,则安全性并不是真正的问题。

标签: algorithm delphi hash numbers


【解决方案1】:

如果性能是一个因素,我建议看看 Peter 下面的 CodeCentral entry。对于大量项目,它的表现非常好。

默认情况下,它使用 P.J. Weinberger ELF hashing function。但也提供了其他的。

【讨论】:

    【解决方案2】:

    几个月前我需要深入研究哈希函数。这是我发现的一些东西。

    您希望散列在整个目标空间(通常为 32 位,但可能是 16 或 64 位)中均匀随机分布命中。您希望输入的每个字符都对输出产生同样大的影响.

    所有简单的散列(如 ELF 或 PJW)简单地循环遍历字符串并在每个字节中通过移位或 mod 进行异或运算,将不符合该标准,原因很简单:添加的最后一个字符具有最大的效果。

    但在 Delphi 和 asm 中有一些非常好的算法。以下是一些参考资料:

    请参阅 1997 年 Dobbs 博士在 burtleburtle.net/bob/hash/doobs.html 上发表的文章
    代码在 burtleburtle.net/bob/c/lookup3.c

    Paul Hsieh (AKA HsiehHash) 的 SuperFastHash 函数 c2004-2008
    www.azillionmonkeys.com/qed/hash.html

    您将在此参考中找到 Delphi(带有可选 asm)源代码:
    http://landman-code.blogspot.com/2008/06/superfasthash-from-paul-hsieh.html
    2008 年 7 月 13 日
    “一年多以前,Juhani Suhonen 要求使用快速哈希值来为他的 哈希表。我建议使用旧的但性能很好的 elf-hash,但也注意到 我最近发现了一个更好的哈希函数。它被称为 SuperFastHash (SFH) 由 Paul Hsieh 创建,用于解决哈希函数的“问题” 从鲍勃詹金斯。 Juhani 询问是否有人可以在 basm 中编写 SFH 函数。 一些人研究了一个 basm 实现并发布了它。”

    哈希传奇继续:
    2007-03-13 Andrew:当散列不好就意味着缓存好
    www.team5150.com/~andrew/blog/2007/03/hash_algorithm_attacks.html
    2007-03-29 安德鲁:打破 SuperFastHash
    floodyberry.wordpress.com/2007/03/29/break-superfasthash/
    2008-03-03 Austin Appleby:MurmurHash 2.0
    murmurhash.googlepages.com/
    SuperFastHash - 985.335173 mb/秒
    查找 3 - 988.080652 mb/秒
    MurmurHash 2.0 - 2056.885653 mb/秒
    提供 c++ 代码 MurmurrHash2.cpp 和对齐只读实现 -
    MurmurHashAligned2.cpp
    //================================================= =========================
    // 这是兰德曼在 C# 中的 MurmurHash2
    //2009-02-25 Davy Landman 用 C# 实现 SuperFashHash 和 MurmurHash2
    //landman-code.blogspot.com/search?updated-min=2009-01-01T00%3A00%3A00%2B01%3A00&updated-max=2010-01-01T00%3A00%3A00%2B01%3A00&max-results=2 //
    //Landman 在 C# 中实现 SuperFastHash 和 MurmurHash2 4 种方式:
    //1:托管代码 2:内联位转换器 3:Int Hack 4:不安全指针
    //SuperFastHash 1:281 2:780 3:1204 4:1308 MB/s
    //MurmurHash2 1:486 2:759 3:1430 4:2196

    对不起,如果上面的结果看起来很乱。我不得不剪切和粘贴它。

    至少上面的参考资料之一为您提供了获取 64 位散列的选项,该散列在信用卡号空间中肯定不会发生冲突,并且可以轻松存储在 MySQL 的 bigint 字段中。

    您不需要加密哈希。它们的 CPU 密集度更高。而“加密”的目的是阻止黑客攻击,而不是避免冲突。

    【讨论】:

      【解决方案3】:

      对于安全问题,所有答案都在连续体上,从最安全最方便。我给你两个答案,一个很安全,一个很方便。鉴于此以及对每个问题的解释,您可以为您的系统选择最佳解决方案。

      您说您的目标是存储此值来代替实际的信用卡,以便您以后可以知道是否再次使用了相同的信用卡号。这意味着它必须只包含信用卡号,并且可能包含统一的盐。包含 CCV、到期日期、姓名等将使其无用,因为相同信用卡号的值可能不同。因此,我们假设您使用相同的盐值填充所有信用卡号,该盐值对于所有条目都将保持一致。

      方便的解决方案是使用FNV(正如 Zebrabox 和 Nick 建议的那样)。这将生成一个 32 位数字,该数字将快速为搜索建立索引。当然,缺点是它最多只能允许 40 亿个不同的数字,并且在实践中会比这更快地产生碰撞。因为它具有如此高的碰撞率,所以蛮力攻击可能会产生足够多的无效结果以使其几乎没有用处。

      安全解决方案是依靠 SHA 哈希函数(越大越好),但需要多次迭代。我建议大约 10,000 个。是的,我知道,10,000 次迭代很多,而且需要一段时间,但是在对抗蛮力攻击速度方面的力量是敌人。如果你想安全,那么你希望它是缓慢的。 SHA 设计为不会对任何大小的输入产生冲突。如果发现冲突,则认为哈希不再可行。 AFAIK SHA-2 系列仍然可行。

      现在,如果您想要一个安全且快速的解决方案在数据库中搜索,那么我建议使用安全解决方案 (SHA-2 x 10K),然后将完整的哈希存储在一个列,然后取前 32 位并将其存储在不同的列中,索引位于第二列。首先对 32 位值执行查找。如果这不产生匹配项,那么您就没有匹配项。如果它确实产生了匹配,那么您可以比较完整的 SHA 值并查看它是否相同。这意味着您正在一个更小的集合上执行完整的二进制比较(哈希实际上是二进制的,但仅表示为字符串以便于人类阅读和在基于文本的协议中传输)。

      如果您真的关心速度,那么您可以减少迭代次数。坦率地说,即使有 1000 次迭代,它仍然会很快。您将需要对您期望数据库获得多大以及可能影响持续时间的其他因素(通信速度、硬件响应、负载等)做出一些现实的判断。您可能会发现您优化了过程中的最快点,这几乎没有实际影响。

      另外,我建议您对完整哈希与 32 位子集的查找进行基准测试。大多数现代数据库系统都相当快,并且包含许多优化,并且经常为我们以简单的方式进行优化。当我们试图变得聪明时,有时我们只是放慢速度。关于过早优化的报价是什么。 . . ?

      【讨论】:

      • 很好的建议,除了 CRC32 对于这个用例来说根本不是一个好的散列函数。它旨在检测传输错误,并且擅长于此,但不承诺碰撞率。像 FNV32 这样的非加密哈希会是更好的选择。
      • 我最终使用了一个看起来是防碰撞的 SHA 实现。
      • 理论上 SHA 不是防碰撞的。但在实践中确实如此。我们只能说机会对你有利。
      • 32 位不是给你大约 40 亿个数字,比 65536 大一点吗?还是我错过了一些明显的东西?顺便说一句,答案很好。
      • @Alister,你是对的。我会更新我的答案。
      【解决方案4】:

      自然数的最佳散列函数让

       f(n)=n
      

      没有冲突 ;)

      【讨论】:

      • (-1) 因为这个答案只包含幽默。不要误会我的意思,我是一个普通的 Slashdot 读者——并且喜欢那里幽默的 cmets! ——我只是觉得这种东西不属于这里。见[本讨论][1]。 [1]:meta.stackexchange.com/questions/17782/…
      【解决方案5】:

      对于非加密方法,您可以查看FNV hash,它速度快,冲突率低。

      作为一个非常快速的替代方案,我也使用了这个算法几年并且几乎没有碰撞问题但是我不能给你一个数学分析它的内在可靠性,但它的价值在于它是

      =编辑 - 我的代码示例不正确 - 现在已修复 =

      在 c/c++ 中

      unsigned int Hash(const char *s)
      {
          int hash = 0;
      
          while (*s != 0)
          {
              hash *= 37;
                  hash += *s;
              s++;
          }
      
          return hash;
      }
      

      请注意,'37' 是一个幻数,因此选择它是因为它是质数

      【讨论】:

      • 这个看起来很有前途,没有任何分析。您可能想指出 37 是一个可选参数(必须是素数)。
      • 您发布的代码与您提供的链接中的代码不匹配。链接中的代码使用异或,而不是加法,它将散列乘以质数,而不仅仅是每个被散列的字节。
      • @Rob Kennedy。代码示例与链接无关 - 它们完全不同。我提供了代码示例作为快速替代方案
      【解决方案6】:

      这似乎是key derivation functions 的情况。看看PBKDF2

      仅使用加密哈希函数(如 SHA 系列)即可获得所需的分布,但对于非常有限的输入空间(如信用卡号),它们很容易被暴力破解,因为这种哈希算法通常设计为尽可能快。

      更新

      好的,安全与您的任务无关。因为您已经有一个数字输入,所以您可以使用这个(帐户)数字以您的哈希表大小为模。如果将其作为字符串处理,则可能确实会遇到错误的分布,因为十位数字仅构成所有可能字符的一小部分。

      另一个问题可能是这些数字形成了分配(帐户)编号的大集群,它们之间存在大面积的未分配数字。在这种情况下,我建议尝试高度非线性的散列函数来传播这个集群。这让我们回到了加密哈希函数。也许好的旧MD5。只需将 128 位散列分成四组 32 位,使用 XOR 组合它们,并将结果解释为 32 位整数。

      虽然没有直接关系,但您也可以查看 Benford's law - 它提供了一些关于为什么数字通常不均匀分布的一些见解。

      【讨论】:

      • 很有趣,但在例程中循环至少 1000 次(对于 PBKDF2)对我来说实际上并不是最佳选择。
      • 如果您需要哈希用于安全关键操作,不必担心执行时间。时间是攻击者的敌人。如果您不是指安全关键操作,那么 PBKDF2 可能真的不是最佳选择。
      【解决方案7】:

      根据定义,加密哈希非常适合您的用例。即使字符很接近,散列也应该很好地分布。

      所以我建议您使用任何加密哈希(例如 SHA-256)和盐。

      【讨论】:

      • 对于简短的输入,简单的加密哈希函数非常简单,因为它们很容易被暴力攻击。
      【解决方案8】:

      如果您需要安全性,请使用加密安全哈希,例如 SHA-256。

      【讨论】:

      • 理想的结果是整数。 SHA-256 对我来说有点矫枉过正。
      • 对于简短的输入,简单的加密哈希函数非常简单,因为它们很容易被暴力攻击。
      • 一个派生函数如 scrypt 使计算更难将是可取的。整数大小的输出将导致大约 65k 个条目发生冲突。这还没有考虑到丢弃部分输出可能导致的密码分析攻击。
      • 信用卡号码超过 17 位(日期有限制,我什至不会给他们两位数的数据。)这很难暴力破解。至于 SHA-256 太过分了——使用它,然后将块一起 XOR 到你决定使用的任何大小。
      • @Daniel:问题不在于功能,而在于输入。没有算法会神奇地将可枚举的范围变成不可枚举的范围。您所能做的就是使用您拥有的最佳原语(在本例中为安全散列),并对其进行迭代以获得额外的安全性。
      猜你喜欢
      • 2015-05-02
      • 2021-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-21
      • 2011-07-02
      相关资源
      最近更新 更多