优惠券代码生成

我想要生成优惠券代码,例如AYB4ZZ2 。 不过,我也希望能够标记使用过的优惠券,并限制其全球数量,比方说N 天真的做法就像“生成N独特的字母数字代码,把它们放到数据库中,并对每个优惠券操作执行数据库search”。

然而,据我所知,我们也可以尝试find一个函数 MakeCoupon(n) ,它将给定的数字转换成一个具有预定义长度的类似优惠券的string。

据我所知, MakeCoupon应该满足以下要求:

  • 是双面的。 这是反MakeNumber(coupon)应该是有效的可计算的。

  • MakeCoupon(n)输出应该是字母数字的,并且应该具有小的和恒定的长度 – 以便它可以被称为人类可读的 。 例如SHA1摘要不会通过这个要求。

  • 实用的独特性。 对于每个自然n <= NMakeCoupon(n)结果应该是完全唯一的或唯一的,例如, MD5是唯一的(具有相同的非常小的碰撞概率)。

  • (这是一个棘手的定义)如何从一个优惠券代码枚举所有剩余的优惠券不应该是显而易见的 – 比方说MakeCoupon(n)MakeCoupon(n + 1)应该在视觉上有所不同。

    例如MakeCoupon(n),它简单地输出n填充零将失败这个要求,因为000001000002实际上并没有“视觉上”的差异。

问:

是否存在满足以下要求的函数或函数发生器? 我的search尝试只会把我带到[CPAN] CouponCode,但是它没有满足相应函数的双射要求。

基本上你可以把你的操作分成几部分:

  1. 不知何故“encryption”你的初始数字n ,以便两个连续的数字产生(非常)不同的结果
  2. 从步骤1的结果构build您的“人类可读的”代码

对于步骤1,我build议使用一个简单的分组密码(例如,具有您select的四舍五入function的Feistel密码 )。 另见这个问题 。

Feistel密码工作在几轮 。 在每一轮中,一半函数被应用到input的一半,结果与另一半交换,并且两半交换。 Feistel密码的好处在于轮回函数不是双向的(每轮之后对轮函数的input保持不变,所以在解密过程中可以重构轮函数的结果)。 所以你可以select你喜欢的任何疯狂的操作:)。 另外Feistel密码是对称的,满足您的第一个要求。

C#中的一个简短例子

 const int BITCOUNT = 30; const int BITMASK = (1 << BITCOUNT/2) - 1; static uint roundFunction(uint number) { return (((number ^ 47894) + 25) << 1) & BITMASK; } static uint crypt(uint number) { uint left = number >> (BITCOUNT/2); uint right = number & BITMASK; for (int round = 0; round < 10; ++round) { left = left ^ roundFunction(right); uint temp = left; left = right; right = temp; } return left | (right << (BITCOUNT/2)); } 

(请注意,在上一轮之后没有交换,在代码中交换只是在构造结果时被撤消)

除了满足你的要求3和4(function是全部的 ,所以对于不同的input你得到不同的输出和input是“完全混乱”根据你的非正式定义),它也是它自己的逆(因此隐含满足要求1),即对于input域中的每个x (本实现中的0..2^30-1 crypt(crypt(x))==x 。 而且在性能要求方面也很便宜。

对于第2步,只需将结果编码到您select的某个基地。 例如,要编码一个30位的数字,你可以使用一个32个字符的6个“数字”(所以你可以编码6 * 5 = 30位)。

C#中这一步的一个例子:

 const string ALPHABET= "AG8FOLE2WVTCPY5ZH3NIUDBXSMQK7946"; static string couponCode(uint number) { StringBuilder b = new StringBuilder(); for (int i=0; i<6; ++i) { b.Append(ALPHABET[(int)number&((1 << 5)-1)]); number = number >> 5; } return b.ToString(); } static uint codeFromCoupon(string coupon) { uint n = 0; for (int i = 0; i < 6; ++i) n = n | (((uint)ALPHABET.IndexOf(coupon[i])) << (5 * i)); return n; } 

对于input0 – 9,这将产生以下优惠券代码

 0 => 5VZNKB 1 => HL766Z 2 => TMGSEY 3 => P28L4W 4 => EM5EWD 5 => WIACCZ 6 => 8DEPDA 7 => OQE33A 8 => 4SEQ5A 9 => AVAXS5 

请注意,这种方法有两个不同的内部“秘密”:首先,循环函数与循环次数一起使用,其次是用于编码encryption结果的字母表。 但是也要注意,所显示的实现在密码意义上绝不是安全的!

还要注意的是,所显示的function是一个完整的双射函数,从某种意义上说,每个可能的6个字符的代码(带有你的字母表中的字符)将产生一个唯一的数字。 为了防止任何人input一些随机的代码,你应该在input数字上定义一些恢复。 例如,只发行前10.000号码的优惠券。 那么,一些随机优惠券代码有效的概率将是10000/2 ^ 30 = 0.00001(这将需要大约50000次尝试才能find正确的优惠券代码)。 如果您需要更多的“安全”,您可以增加位尺寸/优惠券代码长度(见下文)。

编辑:更改优惠券代码长度

更改所得到的优惠券代码的长度需要一些math计算:第一个(encryption)步骤仅对具有偶数位数的位串(对于Feistel密码来说这是必需的)起作用。

在第二步中,使用给定字母表可以编码的位数取决于所选字母的“大小”和优惠券代码的长度。 这个以位为单位的“熵”通常不是一个整数,更不用说偶数。 例如:

使用30个字母字母的5位代码导致30 ^ 5个可能的代码,这意味着ld(30 ^ 5)= 24.53位/优惠券代码。

对于一个四位数的代码,有一个简单的解决scheme:给定一个32个字符的字母,你可以编码* ld(32 ^ 4)= 5 * 4 = 20 *位。 所以你可以将BITCOUNT设置为20,并在代码的第二部分更改for循环直到4 (而不是6

生成一个五位数的代码是有点棘手,并somhow“削弱”algorithm:您可以将BITCOUNT设置为24,只需从30个字符的字母表中产生一个5位代码(从stringstring中删除两个字符,让for循环运行,直到5 )。

但是这不会产生所有可能的5位数字代码:24位只能从encryption阶段获得16,777,216个可能的值,5位数字代码可以编码24,300,000个可能的数字,所以一些可能的代码将永远不会生成。 更具体地说,代码的最后一个位置永远不会包含字母表中的某些字符。 这可以被看作是一个缺点,因为它以一种明显的方式缩小了有效代码集。

在解码优惠券代码时,首先必须运行codeFromCoupon函数,然后检查结果的第25位是否已设置。 这将标记一个无效的代码,您可以立即拒绝。 请注意,在实践中,这可能甚至是一个优势,因为它允许快速检查(例如在客户端)代码的有效性,而不会泄露algorithm的所有内部结构。

如果第25位没有设置,您将调用crypt函数并获取原始数字。

虽然我可能会停靠这个答案,我觉得我需要回应 – 我真的希望你听到我说的,因为它来自很多痛苦的经验。

虽然这项任务在学术上非常具有挑战性,而且软件工程师倾向于挑战自己的意志与解决问题 ,但是如果可能的话,我需要为您提供一些指导。 世界上没有任何零售店,无论如何都没有任何成功,对所产生的每一个实体都没有很好的跟踪。 从每一个库存到每一张优惠券或礼品卡,他们发出这些门。 如果你是这样的话,那就不是一个好的pipe家,因为不是人们会欺骗你,而是因为如果你有足够的东西,你就会做好准备。

现在,让我们来谈谈在你的场景中使用优惠券的过程。

当客户兑换优惠券时,前面会出现某种POS系统? 而这甚至可能是一个在线业务,在那里他们只需input他们的优惠券代码与收银员扫描条形码的registry(我假设这就是我们在这里处理的) ? 所以现在,作为供应商,你说如果你有一个有效的优惠券代码,我会给你一些折扣因为我们的目标是生成可逆的优惠券代码,我们不需要数据库要validation代码,我们可以将其正确地反转! 我是说这只是math吧? 那么,是的,不。

是的,你是对的,这只是math。 事实上,这也是问题,因为破解SSL也是如此。 但是,我将假设我们都认识到在SSL中使用的math比在这里使用的任何东西都要复杂一些而且关键要大得多。

这不是理所当然的事情,你也不应该试图提出一些你肯定没人关心的计划,特别是当涉及到金钱时 。 为了解决一个你不应该试图解决的问题,你正在使自己的生活变得非常困难,因为你需要保护自己免受使用优惠券代码的困扰。

所以这个问题不必要的复杂,可以这样解决。

 // insert a record into the database for the coupon // thus generating an auto-incrementing key var id = [some code to insert into database and get back the key] // base64 encode the resulting key value var couponCode = Convert.ToBase64String(id); // truncate the coupon code if you like // update the database with the coupon code 
  1. 创build具有自动递增键的优惠券表。
  2. 插入该表并获取自动递增键。
  3. Base64将该ID编码为优惠券代码。
  4. 截断该string,如果你想。
  5. 使用刚插入的优惠券将该string存回数据库。

你想要的是所谓的格式保存encryption 。

在不失一般性的情况下,通过以36为基数的编码,我们可以假设我们正在谈论0..M-1整数而不是符号串。 M应该是2的幂。

在select一个密钥并指定M ,FPE给你一个0..M-1 encrypt的伪随机置换及其反向decrypt

 string GenerateCoupon(int n) { Debug.Assert(0 <= n && n < N); return Base36.Encode(encrypt(n)); } boolean IsCoupon(string code) { return decrypt(Base36.Decode(code)) < N; } 

如果你的FPE是安全的,那么这个scheme是安全的:即使他设法猜测与他知道的每个优惠券有关的号码,攻击者也不能以高于O(N / M)的概率产生其他优惠券代码。

这还是一个比较新的领域,所以这种encryptionscheme的实现很less。 这个crypto.SE问题只提到了一个带有Perl / Python绑定的C ++库Botan ,而不是C#。

谨慎的说法:除了FPE尚未被广泛接受的标准之外,您还必须考虑实施过程中可能出现的错误。 如果在线上有很多钱,那么就需要权衡这种风险,而避免数据库的相对小的好处。

你可以使用一个基数为36的数字系统。 假设在coupen输出中需要6个字符。

伪代码为MakeCoupon

MakeCoupon(n){

有一个固定大小的字节数组,例如6.将所有值初始化为0.将数字转换为基数 – 36并将数字存储在数组中(使用整数除法和模运算)现在,对于每个“数字”查找相应的ascii代码假设数字从0..9开始,A..Z以这个输出的六位数作为一个string。

}

现在计算回数是与此操作相反的。

这将适用于非常大的数字(35 ^ 6)与6允许的字符。

  • select一个encryption函数c 。 对c有一些要求,但现在让我们来看看SHA1。

  • select一个密钥k

您的优惠券代码生成function可能是,为数字n

  • 将n和k连接为"n"+"k" (在密码pipe理中称为salting)
  • 计算c(“n”+“k”)
  • SHA1的结果是160位,将它们(例如用base64)编码为ASCIIstring
  • 如果结果太长(正如你所说的那样是SHA1的情况),截断它只保留前10个字母,并命名这个strings
  • 您的优惠券代码是printf "%09d%s" ns ,即零填充的n和截断散列s

是的,猜测优惠券代码的数量是微不足道的(但请参阅下文)。 但是很难生成另一个有效的代码。

您的要求得到满足:

  1. 要计算反向函数,只需读取代码的前9位数字即可
  2. 长度总是19(n的9个数字,加上10个散列字母)
  3. 这是独一无二的,因为前9个数字是唯一的。 最后10个字符也是很可能的。
  4. 即使猜测您使用了SHA1,如何生成哈希也不是很明显。

一些评论:

  • 如果你担心阅读过于明显,你可以像base64编码那样轻松地进行混淆,并在代码中交替使用ns的字符。
  • 我假设你不需要超过十亿个代码,因此在9位数字上打印n,但你当然可以调整参数9和10到你想要的优惠券代码长度。
  • SHA1只是一个选项,您可以使用另一个encryptionfunction,如私钥encryption,但是您需要检查此function在截断时以及提供明文时是否保持强劲。
  • 这在代码长度上不是最佳的,但是具有简单和广泛可用的库的优点。
Interesting Posts