3

我需要生成将用作代金券或类似物品的兑换代码的数字代码。要求是代码是数字的,并且对于收款机操作员的数据输入速度而言相对较短。大约 6 个字符长和数字。我们知道这是一个很小的数字,因此我们制定了一个流程,以便代码可以过期并被重新使用。

我们一开始只使用顺序整数生成器,它在生成唯一代码方面效果很好。这样做的问题是生成的代码是连续的,因此可以预测,这意味着客户可以猜测我们生成的代码并兑换不适合他们的代金券。

我一直在阅读Format Preserving Encryption这似乎对我们有用。我们不需要在任何时候解密代码,因为代码本身是任意的,我们只需要确保它是不可预测的(由普通人)。这对安全并不重要,只是让诚实的人保持诚实。

维基百科文章中引用了各种密码,但我有非常基本的密码学和数学技能,无法编写自己的代码来基于密码实现这一目标。

我想我的问题是,有没有人知道 ac# 的实现,它将一个整数加密为另一个整数并保持相同的长度?

FPE 似乎可以很好地用于将 16 位信用卡号码加密为另一个 16 位号码。我们需要相同类型的东西,但不一定固定为长度,但只要纯值长度与加密值长度匹配即可。

所以以下四个整数将被加密

从 123456 123457 123458 123459

像这样不连续的东西

521482 265012 961450 346582

我对实现此 FPE 的任何其他建议持开放态度,这似乎是一个不错的选择。

编辑

感谢有关仅生成唯一代码并存储它们并检查重复项的建议。现在我们避免这样做,因为我们不想在生成时检查存储。这就是我们使用顺序整数生成器的原因,因此我们不需要检查代码是否唯一。我将重新研究这样做,但现在仍在寻找避免每次生成代码时都必须去存储的方法。

4

3 回答 3

1

我想知道这是否也不会离谱,但让我试一试。该解决方案不需要存储,但需要处理能力(数量很少,但不会像纸笔一样简单)。它本质上是一个自制的 PRNG,但可能具有比内置 PRNG 更适合您想要做的事情的特性。

要制作数字生成器,请使用素数系数和素数模数制作多项式。例如,让 X 代表您发出的第 N 个凭证。然后:

凭证编号 = (23x^4+19x^3+5x^2+29x+3)%65537。这当然只是一个例子;您可以使用任意数量的项,任何您想要的系数的素数,并且您可以使模数尽可能大。事实上,模数根本不需要是素数。它只设置最大凭证号。将系数设为素数有助于减少碰撞。

在这种情况下,凭证 #100、101 和 102 的编号分别为 26158、12076 和 6949。将其视为一种玩具加密,其中系数是您的密钥。不是超级安全,但是没有像你要求的那样小的输出空间对强大的对手是安全的。但这应该可以阻止日常欺诈者。

确认有效的凭证需要使用计算机(但只计算,不存储)。它会遍历几千或几万个输入 X,寻找与呈现给您的凭证匹配的输出 Y。当它找到匹配项时,它可以发出有效凭证的信号。

或者,您可以发出序列号和计算连接在一起的凭证,例如值和校验和。然后,您可以使用您的秘密系数手动运行该值的计算以确认有效性。

只要您不向任何人透露系数,就很难识别输出中的模式。我不确定这是否与您所寻找的一样安全,但发布这个想法以防万一。

我错误地计算了 100 的输出(手动完成但失败了)。刚才更正了。让我添加一些代码来说明如何检查有效凭证:

using System;
using System.Numerics;

namespace Vouchers
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Enter voucher number: ");
            BigInteger input = BigInteger.Parse(Console.ReadLine());
            for (BigInteger i = 0;i<10000000;i++)
            {
                BigInteger testValue = (23 * i * i * i * i + 19 * i * i * i + 5 * i * i + 29 * i + 3) % 65537;
                if(testValue==input)
                {
                    Console.WriteLine("That is voucher # " + i.ToString());
                    break;
                }
                if (i == 100) Console.WriteLine(testValue);
            }
            Console.ReadKey();
        }
    }
}
于 2015-09-08T03:28:33.830 回答
0

一种选择是构建数字的就地随机排列。考虑这段代码:

private static readonly Random random = new Random((int)DateTime.UtcNow.Ticks);

private static int GetRandomPermutation(int input)
{
    char[] chars = input.ToString().ToCharArray();
    for (int i = 0; i < chars.Length; i++ )
    {
        int j = random.Next(chars.Length);
        if (j != i)
        {
            char temp = chars[i];
            chars[i] = chars[j];
            chars[j] = temp;
        }
    }
    return int.Parse(new string(chars));
}

您提到使用其他一些技术遇到性能问题。这种方法做了大量的工作,因此它可能无法满足您的性能要求。无论如何,这是一个整洁的学术练习。

于 2015-09-08T02:39:48.527 回答
0

感谢Blogbeardlc对我的原始帖子的评论提供的帮助。事实证明,无论如何在生成代码时我们都需要访问存储,因此这意味着实施 PRNG 对我们来说是一个更好的选择,而不是乱搞加密。

这就是我们最终做的

  1. 继续使用我们的序号生成器来生成整数
  2. 使用序列号作为种子创建 C# Random 类(PRNG)的实例。
  3. 在我们想要的最小和最大数字范围内生成一个随机数。
  4. 检查重复并重新生成,直到找到唯一的

事实证明,使用带有种子的 c# random 使得在使用序列号作为每一代的种子时,随机数实际上非常可预测。

例如,使用顺序种子的范围在 1 到 999999 之间,我测试了生成 500000 个值而没有一次碰撞。

于 2015-09-14T23:21:37.560 回答