19

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

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

通常,使用盐来避免字典攻击。我假设在这种情况下我们很容易受到攻击,所以我们可能应该将盐与哈希一起存储。

大家觉得这个方法有什么缺陷吗?这是解决这个问题的标准方法吗?

4

15 回答 15

19

让我们做一些数学运算:信用卡号码的长度为 16 位。前七位数字是“主要行业”和发行人编号,最后一位数字是 luhn 校验和。剩下 8 位数字“免费”,总共 100,000,000 个帐号,乘以潜在发行人编号的数量(这不太可能很高)。有些实现可以在日常硬件上每秒执行数百万次哈希,所以无论你做什么加盐,这对蛮力来说都不是什么大问题。

纯属巧合,在寻找提供哈希算法基准的东西时,我发现了这篇关于存储信用卡哈希的文章,它说:

使用简单的单次哈希算法存储信用卡,即使是加盐的,也是很傻的。如果哈希值被泄露,暴力破解信用卡号码太容易了。

...

散列信用卡号时,必须仔细设计散列,以通过使用最强的可用加密散列函数、大盐值和多次迭代来防止暴力破解。

完整的文章非常值得仔细阅读。不幸的是,结果似乎是任何使存储散列信用卡号码“安全”的情况也会使搜索重复项的成本过高。

于 2008-09-19T18:46:13.367 回答
12

我认为人们在考虑这个设计。使用像 sha-256 这样的加盐、高度安全(例如“计算成本高”)的散列,每个记录都有唯一的加盐。

您应该首先进行低成本、高精度的检查,然后只有在检查成功时才进行高成本的确定性检查。

步骤1:

寻找最后 4 位数字的匹配项(也可能是到期日期,尽管可能需要解决一些细微的问题)。

第2步:

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

cc# 的最后 4 位是最独特的(部分原因是它也包括 LUHN 校验位),因此您将执行的深度检查的百分比最终不会匹配(误报率)将非常,非常低(百分之几),相对于天真的“每次都进行哈希检查”设计,这可以为您节省大量开销。

于 2008-09-19T16:52:57.167 回答
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,那么他每天可以恢复几个信用卡号!

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

第二种解决方案

只需让您的支付服务提供商为您的信用卡生成别名即可。

  1. 对于每张信用卡,您只需存储您想要存储的任何内容(例如最后 4 位数字和到期日期)以及信用卡号别名。
  2. 当输入新的信用卡号时,您联系您的支付提供商并提供 CC 号码(或者您将客户重定向到支付提供商,他直接在支付提供商的网站上输入 CC 号码)。作为回报,您将获得信用卡别名!而已。当然,您应该确保您的支付提供商提供此选项,并且生成的别名实际上是安全的(例如,确保他们不会简单地计算信用卡号上的 SHA-1!)。现在,如果盗版者想要恢复信用卡号码,就必须破坏您的系统以及您的支付提供商的系统。

它简单、快速、安全(好吧,至少如果您的支付提供商是的话)。我看到的唯一问题是它将您与您的支付提供商联系在一起。

希望这可以帮助。

于 2009-08-12T18:03:47.133 回答
3

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

使您的数据更安全的一种方法是使每个操作的计算成本很高。例如,如果你 md5 两次,攻击者将花费更长的时间来破解代码。

生成有效的信用卡号并尝试为每个可能的到期日收费是相当简单的。但是,它的计算成本很高。如果你让破解你的哈希变得更昂贵,那么任何人都不值得打扰;即使他们有盐、哈希和你使用的方法。

于 2008-09-19T16:43:17.907 回答
2

我相信我已经找到了解决这个问题的万无一失的方法。如果我的解决方案存在缺陷,请有人纠正我。

  1. 在 EC2、Heroku 等上创建一个安全服务器。该服务器将服务于一个目的且只有一个目的:散列您的信用卡。
  2. 在该服务器上安装安全的 Web 服务器(Node.js、Rails 等)并设置 REST API 调用。
  3. 在该服务器上,使用唯一的盐(1000 个字符)和 SHA512 1000 次。

这样,即使黑客得到了你的哈希值,他们也需要闯入你的服务器才能找到你的公式。

于 2014-08-11T08:02:30.310 回答
2

@Cory R. King

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

于 2008-09-19T16:13:29.053 回答
1

比较哈希是一个很好的解决方案。不过,请确保您不只是用相同的恒定盐对所有信用卡号码进行盐分。在每张卡上使用不同的盐(如有效期)。这应该使您不受字典攻击的影响。

这篇编码恐怖文章

为您存储的每个密码添加一个长的、唯一的随机盐。盐(或随机数,如果您愿意)的目的是使每个密码唯一且足够长,以至于蛮力攻击是浪费时间。因此,用户的密码不是存储为“myspace1”的哈希值,而是最终存储为 128 个字符的随机 unicode 字符串 +“myspace1”的哈希值。你现在完全免疫彩虹桌攻击。

于 2008-09-19T15:58:17.937 回答
1

几乎是个好主意。

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

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

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

你必须自己权衡权衡。如果数据库被盗,添加盐不会使您的数据库安全,它只会使解码变得更加困难。有多难?如果它将攻击从需要 30 秒更改为需要 1 天,那么您一无所获——它仍然会被解码。如果它将它从一天更改为 30 年,那么您已经取得了一些值得考虑的事情。

于 2008-09-19T15:58:59.807 回答
0

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

于 2008-09-19T15:56:42.840 回答
0

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

于 2008-09-19T15:57:55.440 回答
0

SHA1坏了。当然,没有太多关于什么是好的替代品的信息。沙2?

于 2008-09-19T16:02:27.333 回答
0

如果您将卡号的最后 4 位数字与持卡人的姓名(或只是姓氏)和到期日期结合起来,您应该有足够的信息来使记录独一无二。散列对安全性很好,但是您不需要存储/调用盐来复制散列以进行重复检查吗?

于 2008-09-19T16:08:41.390 回答
0

我认为上面暗示的一个好的解决方案是存储一个哈希值,比如卡号、到期日期和名称。这样您仍然可以进行快速比较...

于 2008-09-19T16:13:02.363 回答
0

Sha1了在这里不是问题。所有损坏的意思是计算碰撞(具有相同 sha1 的 2 个数据集)比您预期的更容易。这可能是基于 sha1 接受任意文件的问题,但它与内部散列应用程序无关。

于 2008-09-19T16:13:45.310 回答
0

如果您使用的是像 Stripe / Braintree 这样的支付处理器,让他们来做“繁重的工作”。

它们都提供卡指纹识别,您可以安全地存储在您的数据库中,稍后进行比较以查看卡是否已经存在:

  • Stripe 返回fingerprint字符串 - 见doc
  • Braintree 回归unique_number_identifier string- 见文档
于 2014-09-14T13:29:56.997 回答