生成独特的,难以猜测的“优惠券”代码

我的Rails应用程序需要为用户生成电子优惠券。 每张优惠券都应该有一个独特的优惠券代码,可以在我们的系统上兑换。

例如,免费墨西哥卷饼的优惠券。 User A收到免费卷饼的优惠券,然后User B收到免费卷饼的优惠券。 2张优惠券应该有唯一的优惠券代码。

生成像这样不易伪造的代码的最佳方法是什么? 我不希望用户输入随机数字和兑换其他人优惠券的成功率很高。

我想这就像背面有一个唯一编号的礼品卡就是我要找的东西。

代码需要是不可取的,因为在向用户提供奖励之前,您可以执行的唯一validation是检查他们输入的代码是否存在于“已颁发”代码列表中。

  • 这意味着该格式中所有可能代码的数量远远大于您要发布的代码数量。 根据简单地尝试代码是多么容易(考虑到重复尝试的脚本),那么您可能需要所有可能的代码,使发布的代码超过一百万或十亿或更多。 这听起来很高,但可以在相对较短的字符串中使用。

  • 这也意味着您使用的代码必须在所有可能的代码中随机选择。 这对于避免用户确定大多数有效代码以“AAA”开头是必要的。 更复杂的用户可能会发现你的“随机”代码使用了一个可破解的随机数生成器(Ruby的默认rand()对于随机数据来说速度快且统计上很好,但是以这种方式可以破解,所以不要使用它)。

这种安全代码的起点是加密PRNG的输出。 Ruby有securerandom库,您可以使用它来获取这样的原始代码:

 require 'securerandom' SecureRandom.hex # => "78c231af76a14ef9952406add6da5d42" 

这段代码足够长,可以覆盖任何实际数量的代金券(地球上每个人都有数百万),没有任何有意义的重复或易于猜测的机会。 但是,从物理副本输入有点尴尬。

一旦你知道如何生成一个随机的,几乎无法解释的代码,你的下一个问题就是了解用户体验并决定你可以在可用性的名义上实际损害安全性的程度。 您需要牢记最终用户的价值,因此有人可能会努力获得有效的代码。 我不能为你解答,但可以对可用性做一些一般性的观点:

  • 避免模​​棱两可的人物。 在印刷中,有时很难看出1Il之间的区别。 我们经常从上下文中理解它应该是什么,但是随机化的字符串没有这个上下文。 通过测试0 vs O5 vs S等来尝试几种代码变体将是一个糟糕的用户体验。

  • 使用小写字母或大写字母但不能同时使用。 案例敏感性将不会被用户的某个%年龄理解或遵循。

  • 匹配代码时接受变体。 允许空格和破折号。 也许甚至允许0O表示同样的事情。 这最好通过处理输入文本,使其在正确的情况下,剥离分隔符等。

  • 在打印中,将代码分成几个小部分,用户可以更容易地在字符串中找到它们的位置并一次键入几个字符。

  • 不要让代码太长。 我会建议12个字符,分为3组,每组4个。

  • 这是一个有趣的问题 – 您可能希望扫描代码以查找可能粗鲁的单词,或者避免生成它们的字符。 如果您的代码只包含字符KUFC ,那么冒犯用户的可能性很高。 这通常不是一个问题,因为用户没有看到大多数计算机安全代码,但这些代码将会打印出来!

总而言之,这就是我可以生成可用代码的方式:

 # Random, unguessable number as a base20 string # .reverse ensures we don't use first character (which may not take all values) raw_string = SecureRandom.random_number( 2**80 ).to_s( 20 ).reverse # eg "3ecg4f2f3d2ei0236gi" # Convert Ruby base 20 to better characters for user experience long_code = raw_string.tr( '0123456789abcdefghij', '234679QWERTYUPADFGHX' ) # eg "6AUF7D4D6P4AH246QFH" # Format the code for printing short_code = long_code[0..3] + '-' + long_code[4..7] + '-' + long_code[8..11] # eg "6AUF-7D4D-6P4A" 

这种格式有20**12有效代码,这意味着您可以发布十亿个自己的代码,并且用户只有四分之一的机会猜测一个正确的代码。 在加密圈中会非常糟糕(这段代码对快速本地攻击不安全),但是对于向注册用户提供免费卷饼的网页表单,以及你会注意到某人用脚本尝试了四百万次的情况,这是可以的。

最近我写了优惠券代码gem ,完全一样。 该算法借鉴了Algorithm :: CouponCode CPAN模块。

优惠券代码不仅应该是唯一的,还应该易于阅读和打字,同时它仍然是安全的。 尼尔的解释和解决方案很棒。 这个gem提供了一种方便的方法和奖金validationfunction。

 >> require 'coupon_code' >> code = CouponCode.generate => "1K7Q-CTFM-LMTC" >> CouponCode.validate(code) => "1K7Q-CTFM-LMTC" >> CouponCode.validate('1K7Q-CTFM-LMTO') # Invalid code => nil 

创建不可取的优惠券代码的关键是可能代码的大空间,其中只有一小部分实际上是有效的。 我们以8个字符长的字母数字字符串为例:

alphanumeric = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ – 63个字符

在这种情况下,有63^8 = 248155780267521可能的代码。 这意味着如果您发出十亿个代码,则猜测代码的概率将为10^9/63^8 = 0.000004... – 百万分之四。

但是,它不会阻止运行一个不断尝试的脚本,直到它找出有效的代码。 为了阻止这种暴力攻击,你需要计算每个用户的尝试次数并禁止超过某些限制。

如果您正在寻找能够完全自定义输出优惠券代码(长度,字符集,前缀,后缀和模式)的库,请查看凭证代码生成器js – 用JavaScript编写的库。 用法示例:

 voucher_codes.generate({ length: 8, count: 1000, }); 

它将生成1000个随机唯一代码,每个代码长8个字符。

另一个例子:

 voucher_codes.generate({ pattern: "###-###-###", count: 1000, }); 

它将根据给定的模式生成1000个随机唯一代码。

源代码相对简单。 我敢打赌,如果JS不是你最喜欢的那个,你可以轻松地将它重写为任何其他语言;)

如果您需要全面的优惠券代码管理解决方案(包括powershell攻击预防),您可能会对Voucherify感兴趣。

去做类似的事情:

 class Coupon < ActiveRecord::Base before_save generate_token validates_uniqueness_of :token def generate_token self.token = "#{current_user.id}#{SecureRandom.urlsafe_base64(3)}" end end 

编辑:这是一个更好的答案

例如,您可以使用随机数并通过将所有有效代码存储在数据库中来检查它是否之前未生成。

使用经过validation的生成器绘制随机数( http://en.wikipedia.org/wiki/List_of_pseudorandom_number_generators )。

假设您每天发送333张优惠券,这些优惠券有效期为30天。 所以你必须存储10000个数字并确保伪造者不能偶然找到一个。

如果你的数字有10位有效数字(~32位,~8位hex数字),那么这种事件的概率就超过一百万。 当然你可以使用更多。

我有一个类似的用例,我必须为系统上创建的每个对象生成一个唯一/非重复的代码(在这个问题中,它是一个优惠券)。 我有以下要求:

  • 我希望代码的长度尽可能短。
  • 我意识到,只要确定可能对象数量的位数,代码的长度最终将至少为止。 例如。 如果您生成9999张优惠券,则代码基本上必须至少为4位数。
  • 不应该是顺序/容易猜到的。

我探索了几种生成密钥的方法,包括基于时间戳的密钥,并发现大多数方法都生成长代码。 所以,我决定采用我自己的逻辑如下。

  • 我创建了一个db表,我只创建了一个记录,它保留了到目前为止在系统中创建的对象数。
  • 然后,我将这个数字作为前缀和后缀加上一个字符,每个字符从[a-zA-Z0-9]中随机选择。 此步骤确保即使数字是连续的,除非猜测前缀和后缀,否则无法猜测代码。 基于[a-zA-Z0-9]字符集,代码可能有3782(62 * 61)种可能性。 上面的字符集适用于我,但您可以自由使用您选择的字符集。 有关此线程的最佳答案的一些建议。
  • 每次创建新对象时,db中的对象计数都会增加1。

在这种方法中,代码的字符数将由以下因素确定:

 number of characters of ( count of objects in the system so far ) + 2 

因此,当你开始时,你会得到3个字符,当你达到10个对象时它将是4,当你达到100个对象时它将是5,对于1000它将是6,依此类推。 这样,系统将根据用途自行扩展。

这种方法比首先生成代码然后检查代码是否已经存在于db中的情况更好。 在这种情况下,您将继续生成代码,直到找到尚未生成的代码。