防止信用卡重复使用的最佳方法是什么?

19
我们有一个系统,希望防止同一张信用卡号被注册到两个不同的账户上。由于我们内部不存储信用卡号码,只存储最后四位数字和有效期,所以无法直接比对信用卡号和有效期。
我们目前的想法是,在信用卡注册时在系统中存储信用卡信息的哈希值(SHA-1),通过比对哈希值来确定该卡是否已被使用。
通常,为避免字典攻击,会使用盐。我认为在这种情况下我们是有漏洞的,因此应该将盐值与哈希值一起存储。
你们看到这种方法有什么缺陷吗?这是解决该问题的标准方法吗?
15个回答

20

让我们来算一下:信用卡号共有16位数字。前七位是“主要行业”和发卡行号码,最后一位是Luhn校验和。这留下了8位“自由”,总共有1亿个帐户号码,乘以可能的发卡行号码(不太可能很高)。有实现可以在日常硬件上每秒执行数百万次哈希,因此无论您做什么样的加盐,暴力破解都不是什么大问题。

出于巧合,当我在寻找一些提供哈希算法基准的东西时,我发现了关于存储信用卡哈希的文章,其中说:

即使加了盐,只使用哈希算法的简单单次操作存储信用卡是愚蠢的。 如果哈希被攻破,轻松地暴力破解信用卡号码。

...

在对信用卡号进行哈希处理时,必须仔细设计哈希函数,采用最强大的可用密码哈希函数、大盐值和多次迭代,以防止暴力破解。

完整的文章值得仔细阅读。不幸的是,结果似乎是任何使存储哈希信用卡号码“安全”的情况都会使查找重复项的成本过高。


12

人们对此设计进行了过度思考,我认为。使用像sha-256这样的盐值高度安全(例如“计算密集型”)哈希函数,加上每个记录的唯一盐。

首先应该进行低成本、高准确度的检查,只有当该检查命中时才进行高成本的决定性检查。

步骤1:

查找与最后4位数字(可能也包括有效期限,但其中有一些细节需要处理)相匹配的条目。

步骤2:

如果简单检查符合条件,使用盐,获取哈希值,进行深入检查。

信用卡号的最后4位数字是最独特的(因为它还包括LUHN校验位),因此您将进行的深入检查不会最终匹配的百分比(误报率)非常非常低(不到百分之一),这可以帮助你节省大量相对于朴素的“每次都进行哈希检查”的设计的开销。


听起来不错的想法,应该在比较哈希之前进行最后四位数字的检查。谢谢! - Lars A. Brekken
2
存储最后4位数字可以进一步简化暴力攻击。 - Nick Johnson
@Arachnid 很遗憾,存储最后4位数字是商家的常见做法。然而,如果您绝对不想保留最后4位数字,您可以将过期日期作为第一个测试进行比较。 - Wedge
不错的想法,但我认为最好使用来自“昂贵”哈希的14-16位,而不是最后四位数字作为预选器;昂贵的哈希应包括每个数据库的盐,以防止建立彩虹表,从而有助于攻击多个系统。 - supercat

6
不要存储信用卡号码的简单SHA-1散列值,因为它太容易被破解了(特别是最后4位已知的情况下)。我们公司也遇到了同样的问题:以下是我们的解决方案。
第一种解决方案:
1. 对于每张信用卡,我们存储最后4位数字、到期日期、一个50字节长的随机盐以及CC号码的盐哈希值。我们使用bcrypt哈希算法,因为它非常安全,并且可以调整为任意CPU密集度。我们将其调整为非常昂贵(在我们的服务器上每个哈希大约需要1秒!)。但我想你也可以使用SHA-256,然后根据需要迭代多次。
2. 当输入新的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,则他每天可以恢复几张信用卡号码!
这使我相信您不应该自己计算哈希值:您必须将其委托给其他人。这是第二种解决方案(我们正在迁移到第二种解决方案)。
第二个解决方案:
让您的支付提供商为您的信用卡生成别名即可。
  • 对于每一张信用卡,您可以存储想要存储的任何内容(例如,最后四位数字和过期日期)以及一个信用卡号别名。
  • 当输入新的信用卡号码时,您需要联系支付提供商并提供信用卡号码(或者您将客户重定向到支付提供商,在支付提供商的网站上直接输入信用卡号码)。作为回报,您会得到信用卡别名!就是这样。当然,您应该确保您的支付提供商提供此选项,并且所生成的别名实际上是安全的(例如,请确保他们不仅仅在信用卡号上计算SHA-1!)。现在,如果海盗想要恢复信用卡号,他必须破坏您的系统和您的支付提供商的系统。

它简单、快速、安全(至少如果您的支付提供商是的话)。我唯一看到的问题是它让您受制于您的支付提供商。

希望这能帮助您。


3
PCI DSS规定,您可以使用强大的单向哈希函数存储PAN(信用卡号码)。他们甚至不要求对其进行盐处理。尽管如此,您应该使用每张卡片独特的值进行盐处理。到期日期是一个好的起点,但可能有点太短了。您可以添加卡片的其他信息,例如发行人。您不应使用CVV /安全码,因为不允许存储它。如果您使用到期日期,则当持卡人获得具有相同编号的新卡时,它将被视为不同的卡。这可能是好事或坏事,取决于您的要求。
一种使数据更安全的方法是使每个操作在计算上变得更加昂贵。例如,如果您两次进行md5哈希,攻击者破解代码所需的时间将更长。
生成有效的信用卡号码并尝试通过每个可能的到期日期进行收费相当容易。然而,这是计算上昂贵的。如果您使破解哈希的成本更高,则对于任何人来说都不值得去麻烦;即使他们拥有盐、哈希和您使用的方法。

2
我相信我已经找到了一个解决这个问题的绝佳方法。如果我的方案有漏洞,请有人指出。
  1. 在 EC2、Heroku 等上创建一个安全服务器。这个服务器只有一个目的:对您的信用卡进行哈希。
  2. 在该服务器上安装一个安全的 Web 服务器(Node.js、Rails 等),并设置 REST API 调用。
  3. 在该服务器上,使用一个唯一的盐(1000 个字符)并将其 SHA512 加密 1000 次。
这样,即使黑客获得了您的哈希值,他们也需要攻破您的服务器才能找到您的公式。

2

@Cory R. King

SHA 1算法本身并没有被破解。这篇文章展示了一种方法,可以在不使用暴力破解的情况下生成两个具有相同哈希值的字符串。但是,你仍然无法在合理的时间内生成与特定哈希值相等的字符串。这两者之间有很大的区别。


1
比较哈希值是一个好的解决方案。但要确保不要只使用相同的常量盐对所有信用卡号码进行加密。应该在每张卡上使用不同的盐(如过期日期)。这样可以使你相对免受字典攻击。
来自this Coding Horror article
为每个存储的密码添加一个长而独特的随机盐。盐(或nonce,如果您喜欢)的目的是使每个密码都是唯一的,并且足够长,以至于暴力攻击是浪费时间的。因此,用户的密码不再被存储为“myspace1”的哈希值,而是被存储为128个随机Unicode字符串+“myspace1”的哈希值。现在,您完全免疫彩虹表攻击。

是的,但如果您为每个哈希使用不同的随机盐,则需要在要比较的新值上尝试每个盐。请使用长随机但恒定的字符串+过期日期。 - Matthias Winkelmann
如果您正在使用盐中的过期日期,那么它如何保持不变? - ine
除非他们使用的字典包括到期日期作为盐。 - Ethan Heilman

1

差不多是个好主意。

只存储哈希值是个好主意,它在密码世界中已经使用了几十年。

添加盐似乎是一个公平的想法,确实使攻击者的暴力破解更加困难。但是当你实际检查新的CC是否唯一时,这种盐会让你付出很大的额外努力:你将不得不对新的CC号码进行N次SHA-1哈希运算,其中N是你已经用于比较所有CC的盐的数量。如果你选择了好的随机盐,你就必须为系统中的每张卡片做哈希运算。所以现在变成了你在进行暴力破解。因此,我认为这不是可扩展的解决方案。

你看,在密码世界中,盐不增加任何成本,因为我们只想知道明文+盐是否与我们为该特定用户存储的哈希值匹配。你的要求实际上相当不同。

你必须自己权衡利弊。如果数据库被盗,添加盐并不能使其更安全,只是使解码变得更困难。有多困难呢?如果攻击所需时间从30秒变成了一天,那么你什么也没做——它仍然会被解码。但如果它从一天变成了30年,那么你就值得考虑了。


啊 - 如果SHA-1算法的计算成本如此之高,以至于添加盐值实际上会让黑客需要花费数年时间,那么这也将是如此昂贵,以至于您可能会为添加到10,000个信用卡号码而感到痛苦。但是,您可以随时对其进行测试,以查看它是否会对您的预期客户群体规模造成影响。 - Jeff
2
-1 对不起,但我非常不同意这个答案:您不能像密码一样处理信用卡号码,因为人们可以选择需要的复杂密码(长,带有字母,数字和特殊字符),而信用卡号码只有大约16位数字,其中一半很容易被猜测(前7位是行业标准(请参见Nick Johnson的答案),最后4位存储在数据库中,根据问题(这是常见做法)。所以真的不要“只存储哈希值”。相反,请遵循Wedge的答案或我的答案。 - MiniQuark
1
-1 MiniQuark是正确的。这个方案比其他在这里提出的方案要差。这里没有解决信用卡可能只是可猜测的事实。 - Accipitridae

0
如果您正在使用像Stripe / Braintree这样的支付处理器,请让它们来完成“繁重的工作”。
它们都提供了卡指纹识别功能,您可以安全地将其存储在您的数据库中,并稍后进行比较,以查看该卡是否已存在:
  • Stripe返回fingerprint字符串-请参见doc
  • Braintree返回unique_number_identifier string-请参见doc

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接