防止重复使用信用卡的最佳方法
Posted
技术标签:
【中文标题】防止重复使用信用卡的最佳方法【英文标题】:Best way to prevent duplicate use of credit cards 【发布时间】:2010-09-11 07:37:26 【问题描述】:我们有一个系统,我们希望防止为两个不同的帐户注册相同的信用卡号。由于我们不会在内部存储信用卡号 - 只是最后四位数字和到期日期 - 我们不能简单地比较信用卡号和到期日期。
我们目前的想法是在信用卡注册时在我们的信用卡信息系统中存储一个哈希值 (SHA-1),并比较哈希值以确定该卡之前是否被使用过。
通常使用盐来避免字典攻击。我认为在这种情况下我们很容易受到攻击,因此我们应该将盐与哈希一起存储。
你们发现这种方法有什么缺陷吗?这是解决此问题的标准方法吗?
【问题讨论】:
【参考方案1】:是的,在这种情况下比较哈希应该可以正常工作。
【讨论】:
【参考方案2】:加盐哈希应该可以正常工作。拥有一个每用户加盐的系统应该有足够的安全性。
【讨论】:
我同意。请记住,这使得注册信用卡成为 O(n) 操作,其中 n 是已注册卡的现有数量。加上散列是一项相当昂贵的操作。【参考方案3】:比较哈希是一个很好的解决方案。不过,请确保您不只是用相同的恒定盐对所有信用卡号码进行盐分。在每张卡上使用不同的盐(如有效期)。这应该使您不受字典攻击的影响。
来自this Coding Horror article:
为您存储的每个密码添加一个长且唯一的随机盐。盐(或随机数,如果您愿意)的目的是使每个密码唯一且足够长,以至于蛮力攻击是浪费时间。因此,用户的密码不是存储为“myspace1”的哈希值,而是最终存储为 128 个字符的随机 unicode 字符串 +“myspace1”的哈希值。你现在完全免疫彩虹桌攻击了。
【讨论】:
是的,但是如果你对每个哈希使用不同的随机盐,你需要在你想要比较的新值上尝试每个盐。使用一个长的随机但恒定的字符串 + 到期日期。 如果你用的是salt中的失效日期,它是如何恒定的? 除非他们使用包含过期日期作为盐的字典。【参考方案4】:几乎是个好主意。
只存储哈希值是个好主意,它在密码世界中已经使用了几十年。
添加盐似乎是一个不错的主意,并且确实使暴力攻击对攻击者来说更加困难。但是,当您实际检查以确保新 CC 是唯一的时,该盐会花费您很多额外的精力:您必须对您的新 CC 编号 N 次进行 SHA-1,其中 N 是您拥有的盐的数量已用于您要与之比较的所有 CC。如果您确实选择了好的随机盐,则必须对系统中的所有其他卡进行哈希处理。所以现在是你在做蛮力。所以我会说这不是一个可扩展的解决方案。
您看,在密码世界中,盐不会增加任何成本,因为我们只想知道明文 + 盐是否会散列到我们为该特定用户存储的内容中。你的要求其实很不一样。
您必须自己权衡取舍。如果数据库确实被盗,添加盐不会使您的数据库安全,它只会使解码变得更加困难。有多难?如果它将攻击从需要 30 秒更改为需要一天,那么您一无所获——它仍然会被解码。如果它把它从一天改为 30 年,那么你已经取得了一些值得考虑的事情。
【讨论】:
啊 - 如果 SHA-1 如此昂贵以至于添加盐实际上会花费黑客数年时间,那么它也将如此昂贵以至于您可能会很难添加到 CC 编号10,000。但是您始终可以将其与您的预期客户群规模进行测试,看看它是否会受到伤害。 -1 很抱歉,但我真的不同意这个答案:您不能将信用卡号码视为密码,因为人们可以根据需要选择复杂的密码(长,包含字母、数字和特殊字符),而信用卡号只有大约 16 位数字,其中一半很容易猜到(前 7 个是行业标准(参见尼克约翰逊的回答),最后 4 个存储在数据库中,根据问题(和这是常见的做法)。所以真的不存储“只是哈希”。相反,请按照 Wedge 的回答,或者我的回答。 -1 MiniQuark 是对的。这个方案不如这里提出的其他建议。信用卡可能很容易被猜到的事实在这里没有解决。【参考方案5】:SHA1 是broken。当然,没有太多关于什么是好的替代品的信息。 SHA2?
【讨论】:
SHA1 和 MD5 都被破坏了,因为它可以生成两个具有相同哈希的字符串。在计算哈希的原像时,或者在生成与已知字符串具有相同哈希的字符串时,两者都不会损坏。【参考方案6】:如果您将卡号的最后 4 位数字与持卡人的姓名(或只是姓氏)和到期日期结合起来,您应该有足够的信息来使记录独一无二。散列对安全性很好,但是您不需要存储/调用盐来复制散列以进行重复检查吗?
【讨论】:
是的,你会 - 关键是基于表的攻击需要很长时间才能生成。如果您对每个卡号使用不同的盐,则构建表最多只能为您获得 1 张卡。成本不值得。 我主张反对加盐哈希 - 我认为当 OP 的目的是简单地防止多个数据实例本身不足以匹配时,它是矫枉过正的。【参考方案7】:我认为上面提到的一个很好的解决方案是存储一个哈希值,比如卡号、到期日期和名称。这样您仍然可以进行快速比较...
【讨论】:
【参考方案8】:@Cory R. King
SHA 1 本身并没有损坏。文章显示的是,可以在不到蛮力的时间内生成 2 个具有相同哈希值的字符串。您仍然无法在合理的时间内生成等同于特定哈希的字符串。两者有很大的不同。
【讨论】:
【参考方案9】:Sha1 broken 在这里不是问题。 所有破碎的意思是,计算碰撞(具有相同 sha1 的 2 个数据集)比您预期的更容易。 这可能是基于 sha1 接受任意文件的问题,但它与内部散列应用程序无关。
【讨论】:
【参考方案10】:PCI DSS 声明您可以使用强单向哈希存储 PAN(信用卡号)。他们甚至不需要加盐。也就是说,您应该使用唯一的每张卡值对其进行盐分。到期日是一个好的开始,但可能有点太短了。您可以从卡中添加其他信息,例如发行人。您不应该使用 CVV/安全号码,因为您不能存储它。如果您确实使用了到期日期,那么当持卡人获得一张具有相同号码的新卡时,它将被视为另一张卡。根据您的要求,这可能是好事也可能是坏事。
使您的数据更安全的一种方法是使每个操作的计算成本很高。例如,如果你 md5 两次,攻击者将花费更长的时间来破解代码。
生成有效的信用卡号并尝试在每个可能的到期日进行收费是相当简单的。但是,它的计算成本很高。如果你让破解你的哈希变得更昂贵,那么任何人都不值得打扰;即使他们有盐、哈希和你使用的方法。
【讨论】:
【参考方案11】:我想,人们在考虑这个设计。使用像 sha-256 这样的加盐、高度安全(例如“计算成本高”)的哈希,每个记录都有唯一的盐。
您应该先进行低成本、高精度的检查,然后仅在检查成功时才进行高成本的确定性检查。
第 1 步:
查找最后 4 位数字的匹配项(可能还有到期日期,尽管可能需要解决一些细微的问题)。
第 2 步:
如果简单检查命中,使用盐,获取哈希值,进行深度检查。
cc# 的最后 4 位是最独特的(部分原因是它也包括 LUHN 校验位),因此您将执行的深度检查的百分比最终不会匹配(误报率)将非常非常低(百分之几),相对于天真的“每次都进行哈希检查”设计,这可以为您节省大量开销。
【讨论】:
这听起来是个好主意,在比较哈希之前检查最后 4 位数字。谢谢! 存储最后 4 位数字可以进一步简化暴力攻击。 @Arachnid 遗憾的是,存储最后 4 位数字是商家的常见做法。但是,如果您绝对不想保留最后 4 位数字,则可以将到期日期作为第一个测试进行比较。 好主意,尽管我认为使用“昂贵”哈希中的 14-16 位而不是最后四位作为预选择器会更好;昂贵的哈希应该包括每个数据库的盐,以防止构建彩虹表,这将有助于对多个系统的攻击。 【参考方案12】:让我们算一下:信用卡号的长度为 16 位。前七位数字是“主要行业”和发行人编号,最后一位数字是 luhn 校验和。剩下 8 位数字“免费”,总共 100,000,000 个帐号,乘以潜在发行人编号的数量(这不太可能很高)。有些实现可以在日常硬件上每秒执行数百万次哈希,所以无论你做什么加盐,这对蛮力来说都不是什么大问题。
纯属巧合,在寻找提供哈希算法基准的东西时,我找到了this article about storing credit card hashes,上面写着:
使用简单的单次哈希算法存储信用卡,即使在加盐的情况下,也是很傻的。如果哈希值被泄露,暴力破解信用卡号码太容易了。
...
在对信用卡号进行哈希处理时,必须仔细设计哈希处理,以通过使用最强的可用加密哈希函数、大盐值和多次迭代来防止暴力破解。
整篇文章非常值得仔细阅读。不幸的是,结果似乎是任何使存储散列信用卡号码“安全”的情况也会使搜索重复项的成本高得令人望而却步。
【讨论】:
【参考方案13】:不要存储信用卡号的简单 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 年的时间内破坏一张信用卡。因此,如果您的数据库中有 10 万张信用卡,并且盗版者有 很多 个 CPU,那么他每天可以恢复几个信用卡号!
这使我相信您不应该自己计算哈希:您必须将其委托给其他人。这是第二种解决方案(我们正在迁移到第二种解决方案)。
第二种解决方案
只需让您的支付服务提供商为您的信用卡生成一个别名。
-
对于每张信用卡,您只需存储您想要存储的任何内容(例如最后 4 位数字和到期日期)加上信用卡号别名。
当输入一个新的信用卡号时,您联系您的支付提供商并为其提供 CC 号码(或者您将客户重定向到支付提供商,他直接在支付提供商的网站上输入 CC 号码)。作为回报,您将获得信用卡别名!而已。当然,您应该确保您的支付提供商提供此选项,并且生成的别名实际上是安全的(例如,确保他们不会简单地计算信用卡号上的 SHA-1!)。现在,如果盗版者想要恢复信用卡号码,他必须破坏您的系统加上您的支付提供商的系统。
简单、快速、安全(至少,如果您的支付提供商是的话)。我看到的唯一问题是它将您与您的支付提供商联系在一起。
希望这会有所帮助。
【讨论】:
【参考方案14】:我相信我已经找到了解决这个问题的万无一失的方法。如果我的解决方案有缺陷,请有人纠正我。
-
在 EC2、Heroku 等设备上创建一个安全服务器。该服务器的用途只有一个:散列您的信用卡。
在该服务器上安装安全的 Web 服务器(Node.js、Rails 等)并设置 REST API 调用。
在该服务器上,使用唯一的盐(1000 个字符)和 SHA512 1000 次。
这样,即使黑客获取了您的哈希值,他们也需要侵入您的服务器才能找到您的公式。
【讨论】:
【参考方案15】:如果您使用的是 Stripe / Braintree 之类的支付处理器,请让他们完成“繁重的工作”。
它们都提供卡指纹识别,您可以安全地将其存储在数据库中,稍后进行比较以查看卡是否已经存在:
Stripe 返回fingerprint
字符串 - 请参阅 doc
Braintree 返回unique_number_identifier string
- 见doc
【讨论】:
以上是关于防止重复使用信用卡的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章