关于哈希:防止重复使用信用卡的最佳方法

Best way to prevent duplicate use of credit cards

我们有一个系统,希望防止两个不同帐户注册相同的信用卡号。由于我们不在内部存储信用卡号-仅存储最后四位数字和有效期-我们不能简单地比较信用卡号和有效期。

我们当前的想法是在注册卡时在我们的信用卡信息系统中存储一个哈希(SHA-1),并比较哈希值以确定以前是否使用过该卡。

通常,使用salt来避免字典攻击。我认为在这种情况下我们很脆弱,因此我们可能应该将salt和哈希值一起存储。

你们看到这种方法有什么缺陷吗?这是解决此问题的标准方法吗?


让我们做一些数学运算:信用卡号长16位数字。前七个数字是"主要行业"和发行者编号,最后一个数字是luhn校验和。这样就剩下8位数字"免费",表示总共100,000,000个帐号乘以潜在的发卡行号码的数量(这不太可能很高)。有一些实现可以在日常硬件上每秒执行数百万个哈希的实现,因此,无论您进行什么繁琐的工作,这对于强力来说都不会是什么大问题。

巧合的是,当寻找一些给出哈希算法基准的东西时,我发现了这篇有关存储信用卡哈希值的文章,内容为:

Storing credit cards using a simple single pass of a hash algorithm, even when salted, is fool-hardy. It is just too easy to brute force the credit card numbers if the hashes are compromised.

...

When hashing credit card number, the hashing must be carefully designed to protect against brute forcing by using strongest available cryptographic hash functions, large salt values, and multiple iterations.

全文值得一读。不幸的是,结果似乎是,任何使存储散列信用卡号"安全"的情况都将使得搜索重复项的成本过高。


我认为人们已经开始考虑此设计。使用salt分,高度安全(例如,"计算上很贵")的哈希(如sha-256),并按记录记录唯一的salt。

您应该首先进行低成本,高精度检查,然后仅在该检查命中时再进行高成本的最终检查。

步骤1:

查找与最后4位数字匹配的内容(可能还有到期日期,尽管那里可能需要解决一些细微问题)。

步骤2:

如果简单检查成功,请使用salt,获取哈希值,然后进行深度检查。

cc#的最后4位数字是唯一的(部分原因是它还包括LUHN校验位),因此您将执行的深度校验的百分比最终将不匹配(误报率)将会非常非常低(百分之一的百分比),相对于朴素的"每次都执行哈希检查"设计,这可以为您节省大量的开销。


不要存储简单的信用卡号SHA-1,这太容易破解了(特别是因为知道后4位数字)。我们公司遇到了同样的问题:这是我们解决的方法。

第一个解决方案

  • 对于每张信用卡,我们存储最后4位数字,到期日期,长随机salt(50字节长)和CC号的加salt哈希值。我们使用bcrypt哈希算法是因为它非常安全,并且可以根据需要调整为CPU密集型。我们将其调整为非常昂贵(服务器上每个哈希大约需要1秒!)。但是我想您可以改用SHA-256,并根据需要进行多次迭代。
  • 输入新的抄送号码后,我们首先查找所有以相同的4位数字结尾并且具有相同的到期日期的现有抄送号码。然后,对于每个匹配的CC,我们检查其存储的salt腌哈希是否与根据其salt和新CC编号计算出的salt腌哈希匹配。换句话说,我们检查是否hash(stored_CC1_salt+CC2)==stored_CC1_hash
  • 由于我们的数据库中大约有10万张信用卡,因此我们需要计算大约10个散列,因此我们可以在大约10秒内得到结果。在我们的情况下,这很好,但是您可能希望将bcrypt调低一点。不幸的是,如果您这样做,此解决方案将不太安全。另一方面,如果将bcrypt调整为占用更多CPU资源,则将花费更多时间来匹配CC编号。

    尽管我相信此解决方案比简单地存储CC号的未加salt哈希更好,但它并不能防止非常有动机的海盗(设法获得数据库的副本)破坏信用卡中的一张信用卡。平均时间为2到5年。因此,如果您的数据库中有10万张信用卡,并且海盗拥有大量CPU,那么他每天就可以收回一些信用卡号!

    这使我相信您不应该自己计算哈希:您必须将其委托给其他人。这是第二个解决方案(我们正在迁移到第二个解决方案)。

    第二种解决方案

    只需让您的付款服务提供商为您的信用卡生成一个别名。

  • 对于每张信用卡,您只需存储要存储的任何内容(例如最后4位数字)


    PCI DSS指出,您可以使用强大的单向哈希存储PAN(信用卡号)。他们甚至不需要将其腌制。也就是说,您应该为每张卡添加唯一的salt。到期日期是一个好的开始,但可能有点太短了。您可以从卡中添加其他信息,例如发卡行。您不应该使用CVV /安全号码,因为您无法存储它。如果您确实使用了有效期,那么当持卡人获得一张具有相同编号的新卡时,它将被视为另一张卡。这可能是好事,也可能是坏事,这取决于您的要求。

    使数据更安全的一种方法是使每个操作的计算量都很大。例如,如果您对md5进行两次操作,攻击者将需要更长的时间来破解代码。

    生成有效的信用卡号并尝试对每个可能的到期日期进行收费都是很简单的。然而,这在计算上是昂贵的。如果使散列破解变得更加昂贵,那么任何人都不会值得去打扰;即使它们有salt,哈希和您使用的方法。


    n


    @Cory R. King

    SHA 1本身并没有损坏。文章显示的是,有可能在不到蛮力的时间内生成两个具有相同哈希值的字符串。您仍然无法在合理的时间内生成等于SPECIFIC哈希的字符串。两者之间有很大的不同。


    一个好主意。

    仅存储散列是一个好主意,它已经在密码世界中使用了数十年。

    加salt似乎是个好主意,确实使蛮力攻击对攻击者而言要困难得多。但是,当您实际检查以确保新的CC唯一时,该salt将花费您很多额外的精力:您必须将新CC的SHA-1数SHA-1次,其中N是您所salt的数量已经用于您要与之进行比较的所有抄送。如果确实选择了良好的随机salt,则必须对系统中其他所有卡进行哈希处理。所以现在是您在做蛮力。所以我会说这不是可扩展的解决方案。

    您会看到,在密码世界中,salt不增加成本,因为我们只想知道明文salt是否会哈希到我们为该特定用户存储的内容。您的要求实际上完全不同。

    您将不得不权衡一下自己。如果salt被盗,添加salt并不能使您的数据库安全,只会使解码变得更加困难。有多少难度?如果将攻击从需要30秒更改为需要一天,则您一无所获-仍将被解码。如果将其从一天更改为30年,您将获得一些值得考虑的东西。


    比较散列是一个很好的解决方案。但是,请确保您不只是将所有信用卡号都用相同的常量加salt。在每张卡上使用不同的salt(例如有效期限)。这应该使您完全不受字典攻击的影响。

    摘自《编码恐怖》的这篇文章:

    Add a long, unique random salt to each password you store. The point of a salt (or nonce, if you prefer) is to make each password unique and long enough that brute force attacks are a waste of time. So, the user's password, instead of being stored as the hash of"myspace1", ends up being stored as the hash of 128 characters of random unicode string +"myspace1". You're now completely immune to rainbow table attack.


    n


    Sha1损坏在这里不是问题。
    所有损坏的意思是,比您期望的更容易计算冲突(两个具有相同sha1的数据集)。
    这对于基于sha1接受任意文件可能是一个问题,但与内部哈希应用程序无关。


    我认为上述建议的一个好的解决方案是存储一个散列值,例如卡号,有效期和名称。这样,您仍然可以进行快速比较...


    如果将卡号的后4位与持卡人的姓名(或姓氏)和有效期结合起来,则您应该有足够的信息来使记录唯一。散列对于安全性来说很好,但是您是否不需要存储/调用salt来复制散列以进行重复检查?


    SHA1损坏。当然,关于什么是好的替代品并没有太多的信息。 SHA2?


    加salt的哈希应该可以正常工作。每用户拥有一个salt分系统应该具有足够的安全性。


    是的,在这种情况下,比较散列应该可以正常工作。