【问题标题】:Low collision email hashing algorithm?低冲突电子邮件哈希算法?
【发布时间】:2011-11-10 13:12:41
【问题描述】:

背景:

我们正在构建一个邮件工具,目前已将emailaddresses 分隔到一个单独的表中,因此单个emailaddress 仅存储一次,而是由其id 引用。我们发现这是一个好主意,因为每封电子邮件的收件人数量可能很大,而且大多数电子邮件地址可能会收到超过 100 封电子邮件。

但是,当用户将emailaddresses 导入list 或类似操作时,我们首先需要批量插入以确保所有电子邮件地址都具有ids,我们只需忽略冲突即可。但是,当我们想要将它们插入到list 中时,我们必须一个接一个地获取emailaddresses,或者使用带有电子邮件地址的巨大IN 查询(​​因为list 引用emailaddress by id ),不是很诱人!

编辑:用户可能要导入 100.000 多个电子邮件地址,对于 1000 个左右的电子邮件地址,当然逐个查询并不是真正的问题。

问题:

所以一个想法是散列每个emailaddress 并将其用作id。这意味着,我们可以预测所有emailaddressesid,而无需查询它们。但是有没有什么好的算法,因为存储 16bytes/128bit+ 有点达不到目的...... 64bit 应该足够吗?鉴于这也应该被索引,这将不胜感激。

有什么建议吗?如果我们只是从 MD5 中取出前 8 个字节会怎样? SHA1 的 8 字节更好吗?也许有更专业的算法?我并不完全了解碰撞概率,但我很好奇现有算法在精简时的效果如何,因为电子邮件是小写的,主要是字母或数字。 (注意数据集可能会很大)

附言。我们使用的是 PHP,所以它在一定程度上限制了我们实现特殊算法的能力。

【问题讨论】:

    标签: php sql hash md5 probability


    【解决方案1】:

    不确定我是否理解您的用例,但在电子邮件地址列上设置唯一键约束...

    【讨论】:

    • 重点是根据一些统计数据,平均电子邮件地址约为 25 个字符。现在考虑向 1.000.000 个电子邮件地址发送一封电子邮件,这些电子邮件地址至少消耗约 26MB 的数据……而存储 32 位 ID 意味着约 4MB(或 64 位散列约 8MB)。有点做作,当然还有更多的数据要存储,但这是最大的单一数据来源,这样做至少会占用总存储量的一半。
    • 您想向 1000000 个地址发送电子邮件或存储它们?从您的问题中,我了解到您的问题是在将它们插入数据库时​​
    • 发送电子邮件并保存统计信息要求您为该“电子邮件”的每个收件人都有一个“收件人”记录,其中可以存储点击数据、交付状态等。我的想法是,而不是在每个“收件人”记录中一遍又一遍地存储相同的约 25 字节电子邮件,“收件人”记录反而将 ID 保留到唯一“电子邮件地址”列表中……因此存储消耗大大降低。
    • 嗯,是的,如果这是您想要做的,您需要一个包含电子邮件地址列的表,以及另一个指向该表的表,该表具有交付状态的记录。它的基本数据库优化。仍然不知道您不想在数据库中使列“唯一”有什么意义。你研究过分片吗?
    • 是的,这就是目前的情况。问题是,当电子邮件地址作为文本输入时,当前无法将具有传递状态的表创建为批处理操作......因为它们首先需要被拆分并批量插入到“电子邮件地址”表中......然后需要在“delivery”表中为它们中的每一个创建一条记录......这是问题所在,除非我们查询“emailaddress”表,否则我们没有要插入到“ delivery”-table...从“emailaddress”-table 中获取 ID 的唯一方法是一个接一个?
    【解决方案2】:

    您当前的方法及其局限性存在大量问题。

    其中大部分问题都可以通过维护一个以 ID 作为主键的电子邮件地址表(在 MySQL 或 SQLite 上自动递增,在其他地方排序)和电子邮件地址上的唯一索引来解决。

    为什么您的“用户”会处理大量电子邮件地址列表,目前尚不清楚。听起来您的大部分数据(即特定列表中的收件人)并未在您的数据库中维护。您永远不应该“一个接一个地获取电子邮件地址或使用带有电子邮件地址的大量 IN 查询”。

    减少 md5 或 sha 的输出会破坏散列的唯一性并增加冲突的可能性。

    【讨论】:

    • 是的,它确实减少了唯一性......但在最好的情况下,除非我搞砸了,否则 64 位将给我 50% 的机会在 50 亿哈希中发生冲突(这当然是完全可以接受的)。但是,我猜最大的问题是,当您考虑到 emailaddressess 的外观时,应该有多少“位”消失,只有字母数字字符、小写字母并添加到修剪 MD5/SHA1 中。
    【解决方案3】:

    在你做任何激烈的事情之前,检查你的查询计划(你如何做取决于你使用的特定数据库服务器,检查它的文档)。

    查看您是否无法获得索引以处理电子邮件地址。这应该会加快一些速度,尽管规划器可能会因为您插入大量数据而跳过它们。

    当(且仅当)您尝试过时,您可以查看散列问题。

    我不知道任何专门为散列电子邮件地址而设计的算法,虽然您可以使用 MD5,但它的设计目的是在冲突概率必须非常小以至于基本上不会发生的情况下使用(我认为没有人有在野外发现了 MD5 碰撞)。这可以做到,但计算成本很高。如果使用 SHA,情况会更糟。

    在你的情况下,我会建议一些更简单的方法:首先,因为我们可以假设所有电子邮件都在表单中

    <someName>@<someServer>
    

    我要做的是将电子邮件分成两部分,从每个部分中删除所有非字母、非数字字符。

    接下来,我们可以计算两个部分中的每一个的数值,我们通过将每个单独字母的 ascii 值相加得到(你剥离了其他所有内容,所以多字节字符不会有问题) .

    此时剩下要做的就是将这两个和合并起来,由于我们可以预期可能的发送者会少得多,我们可以只用两个字节来保存服务器的名称。

    在伪代码中:

    function emailHash(namePart, serverPart){
      $someName = asciiStrip(namePart)
      $someServer = asciiStrip(serverPart)
      $someNameSum = 0 
      $someServerSum = 0 
      foreach($letter in $someName){
        $someNameSum += asciiValue($letter)
      }
      foreach($letter in $someServer){
        $someServerSum += asciiValue($letter)
      }
      return ($someNameSum % 2^6)*2^2 + $someServerSum % 2^2
    }
    

    基于 cmets 编辑

    你说得对,那个人真的很穷。但是,您还可以做另一件有趣的事情,尽管实现起来会有点困难。

    在我们去掉外来字符后,只有 36 个可能的字符,所以我们只需要 6 位来存储每个值。使用 48 位存储用户名部分,可以存储来自电子邮件地址的 8 个字符。碰撞会有多低?

    可以通过以某种方式取消数字(例如在将它们除以二后存储它们)来改进,这样我们总共只处理 32 个数字。然后可以将每个数字仅存储在 5 位中,总共 9 个字符。

    如果这不能提供足够低的碰撞率,您可能必须使用 MD5,它应该(假设算法给出完美分布)只会给您数十亿分之一的碰撞机会。

    【讨论】:

    • 你基本上建议给用户名部分更多的“准确性”,而在服务器名部分失去一些“准确性”。有趣..但是你在这里建议的散列算法真的很差。它会认为“abc@x.com”等于“bbb@x.com”,即使它们没有任何共同之处。 md5() 和它的朋友来这里是有原因的
    • 我喜欢这个想法,但具体实现很危险......“ab@example.com”会与“ba@example.com”等发生冲突。
    猜你喜欢
    • 2015-11-17
    • 1970-01-01
    • 2014-04-10
    • 2012-10-10
    • 1970-01-01
    • 2016-07-30
    • 1970-01-01
    • 2018-10-08
    • 2011-12-12
    相关资源
    最近更新 更多