我们有一个系统,我们希望阻止为两个不同的帐户注册相同的信用卡号.由于我们不在内部存储信用卡号码 - 只是最后四位数字和到期日期 - 我们不能简单地比较信用卡号码和到期日期.
我们目前的想法是在注册卡时在我们的信用卡信息系统中存储散列(SHA-1),并比较散列以确定之前是否使用过卡.
通常,使用salt来避免字典攻击.我认为在这种情况下我们很容易受到攻击,因此我们应该将哈希值与哈希一起存储起来.
你们看到这种方法有什么缺陷吗?这是解决这个问题的标准方法吗?
我们做一些数学计算:信用卡号码长16位.前七位是"主要行业"和发行人数字,最后一位是luhn校验和.留下8位"免费",总计100,000,000个账号,再乘以潜在发行人数的数字(不太可能非常高).有些实现可以在日常硬件上每秒进行数百万次哈希,所以无论你做什么腌制,这对蛮力来说都不是什么大问题.
纯粹巧合的是,在寻找给出散列算法基准的东西时,我发现这篇关于存储信用卡哈希的文章,其中说:
使用哈希算法的简单单次传递来存储信用卡,即使在加盐时也是如此,这是非常愚蠢的.如果哈希值受到损害,那么就很容易对信用卡号码进行暴力破解.
...
散列信用卡号时,必须仔细设计散列,以通过使用最强的可用加密散列函数,大盐值和多次迭代来防止暴力强制.
完整的文章非常值得仔细阅读.不幸的是,结果似乎是任何使得存储哈希信用卡号码"安全"的情况也会使搜索重复数据的成本过高.
我想,人们正在考虑设计这个问题.使用像sha-256这样的盐渍的,高度安全的(例如"计算上昂贵的")散列,以及每个记录的独特盐.
您应首先进行低成本,高精度检查,然后仅在检查结果时进行高成本的最终检查.
步骤1:
寻找与最后4位数的匹配(也可能是exp.日期,尽管那里可能需要解决一些细微之处).
第2步:
如果简单检查命中,使用salt,获取哈希值,进行深入检查.
cc#的最后4位是最独特的(部分原因是因为它还包括LUHN校验位),因此深入检查的百分比将与最终匹配(误报率)非常一致,非常低(百分之几),相对于天真的"每次进行哈希检查"设计,可以节省大量的开销.
难道不是存储简单的SHA-1的信用卡号,这将是方式来容易开裂(尤其是因为最后4位数字都知道).我们公司遇到了同样的问题:这就是我们如何解决它的问题.
第一解决方案
对于每张信用卡,我们存储最后4位数,到期日期,长随机盐(50字节长)和CC号的盐渍哈希值.我们使用bcrypt哈希算法,因为它非常安全,可以根据需要调整为CPU密集型.我们将其调整为非常昂贵(在我们的服务器上每个哈希大约1秒!).但我想你可以使用SHA-256代替并根据需要迭代多次.
当输入新的CC编号时,我们首先查找以相同的4位数结尾且具有相同的到期日期的所有现有CC编号.然后,对于每个匹配的CC,我们检查其存储的盐渍散列是否与从其盐和新CC号计算的盐渍散列相匹配.换句话说,我们检查是否hash(stored_CC1_salt+CC2)==stored_CC1_hash
.
由于我们的数据库中有大约10万张信用卡,我们需要计算大约10个哈希值,因此我们得到的结果大约需要10秒钟.在我们的例子中,这很好,但你可能想稍微调整bcrypt.不幸的是,如果你这样做,这个解决方案将不那么安全.另一方面,如果将bcrypt调整为更加CPU密集型,则需要更多时间来匹配CC编号.
虽然我相信这个解决方案的方式不是简单地存储CC数的无盐哈希更好,它不会阻止一个很上进的海盗(谁管理,以获得数据库的副本)打破一个信用卡的平均时间2至5年.因此,如果您的数据库中有100k的信用卡,并且盗版者拥有大量的CPU,那么他每天都可以恢复一些信用卡号码!
这让我相信你不应该自己计算哈希:你必须将其委托给其他人.这是第二个解决方案(我们正在迁移到第二个解决方案).
二解决方案
只需让您的付款服务提供商为您的信用卡生成别名即可.
对于每张信用卡,您只需存储您想要存储的任何内容(例如最后4位数和到期日期)以及信用卡号别名.
当输入新的信用卡号码时,您联系您的支付提供商并给它CC号码(或者您将客户端重定向到支付提供商,他直接在支付提供商的网站上输入CC号码).作为回报,您将获得信用卡别名!而已.当然,您应该确保您的支付提供商提供此选项,并且生成的别名实际上是安全的(例如,确保他们不只是计算信用卡号上的SHA-1!).现在,如果他想要恢复信用卡号码,盗版者必须打破你的系统加上你的支付提供商的系统.
它很简单,速度快,安全性好(至少如果您的支付服务提供商是这样).我看到的唯一问题是它将您与您的支付提供商联系在一起.
希望这可以帮助.