关于生成不可追踪的发票ID的想法

时间:2013-08-10 06:26:04

标签: c# sql data-structures numbers integer

我想在我的应用中为客户打印发票。每张发票都有发票ID 。我希望ID为:

  • 顺序(最近输入的ID迟到)
  • 32位整数
  • 不容易可跟踪,如1 2 3,以便人们无法分辨我们销售的商品数量。

我自己的想法:  自特定日期&以来的数量时间(例如1/1/2010 00 AM)。

如何生成这些数字?

10 个答案:

答案 0 :(得分:10)

我不喜欢使用时间的想法。你可以遇到各种各样的问题 - 时间差异,一秒钟内发生的几个事件等等。

如果您想要一些顺序且不易追踪的内容,那么如何为每个新Id生成1和任意之间的随机数(例如100)。每个新ID都是前一个Id +随机数。

您还可以为ID添加常量,使其看起来更令人印象深刻。例如,您可以为所有ID添加44323,并将ID 15,23和27转换为44338,44346和44350。

答案 1 :(得分:7)

您的问题有两个问题。一个是可解决的,一个不是(有你给出的约束)。

可解决:不可取的数字

第一个很简单:当客户有权访问一组有效的发票号时,客户很难猜出有效的发票号(或下一个有效的发票号)。

您可以使用约束来解决此问题:

将发票编号分为两部分:

  1. 一个20位前缀,取自一系列递增数字(例如自然数0,1,2,......)
  2. 随机生成的10位后缀
  3. 使用这些方案,有100万有效发票号码。您可以预先计算它们并将它们存储在数据库中。显示发票号时,请检查它是否在您的数据库中。如果不是,则无效。

    使用SQL序列分发号码。当发出新的(即未使用的)发票号时,增加seuqnce并从预先计算的列表中发出第n个数字(按值排序)。

    无法解决:猜测客户数量

    如果您想阻止拥有多个有效发票号码的客户猜测您已发出多少发票号码(以及您拥有多少客户):这是不可能的。

    你有一个所谓的“German tank problem”的变体形式。在第二次世界大战中,盟国使用印在德国坦克齿轮箱上的序列号来猜测德国制造了多少坦克。这很有效,因为序列号没有间隙地增加。

    但即使你用差距增加数字,德国坦克问题的解决方案仍然有效。这很容易:

    1. 您使用here描述的方法猜测最高发票数
    2. 您猜测两个连续发票号码之间的平均差异,并将数字除以此值
    3. 您可以使用线性回归来获得稳定的delta值(如果存在)。
    4. 现在你对发票数量的数量级有了很好的猜测(200,15000,50万等)。

      这可以在那里(理论上)存在两个连续发票号的平均值。即使使用随机数发生器,通常也是如此,因为大多数随机数发生器被设计成具有这样的平均值。

      有一个对策:您必须确保两个连续数字的间隙不存在平均值。具有此属性的随机数生成器可以非常容易地构建。

      示例:

      1. 从最后一个发票编号加上一个作为当前编号
      2. 开始
      3. 将当前数字乘以随机数> = 2。这是您当前的新号码。
      4. 获取一个随机位:如果该位为0,则结果为您当前的数字。否则请返回第2步。
      5. 虽然这在理论上有效,但你很快就会用完32位整数。

        我认为这个问题没有实用的解决方案。两个连续数字之间的间隙具有平均值(方差很小),您可以轻松猜出已发布数字的数量。或者你将很快耗尽32位数。

        Snakeoil(非工作溶液)

        不要使用任何基于时间的解决方案。时间戳通常很容易猜到(可能会在发票上的某处打印一个大致正确的时间戳)。使用时间戳通常会使攻击者更容易,而不是更难。

        不要使用不安全的随机数。大多数随机数生成器不具有加密安全性。它们通常具有对统计有利但对您的安全性有害的数学属性(例如可预测的分布,稳定的平均值等)。

答案 2 :(得分:5)

一种解决方案可能涉及异或(XOR)二进制位图。结果函数是可逆可能会生成非连续数字(如果最低有效字节的第一位设置为1),并且非常容易实现。并且,只要您使用可靠的序列生成器(例如,您的数据库),就不需要线程安全问题。

According to MSDN,'[异或运算]的结果是真的,当且仅当其中一个操作数为真时。反向逻辑说相等的操作数总是会导致错误。

作为一个例子,我刚在Random.org上生成了一个32位序列。就是这样:

11010101111000100101101100111101

此二进制数转换为十进制 3588381501 ,十六进制转换为 0xD5E25B3D 。我们称之为基本密钥

现在,让我们使用([基本键] XOR [ID])公式生成一些值。在C#中,这就是加密函数的样子:

    public static long FlipMask(long baseKey, long ID)
    {
        return baseKey ^ ID;
    }

以下列表包含一些生成的内容。其栏目如下:

  • ID
  • ID的二进制表示
  • XOR操作后的二进制值
  • 最终,'加密'十进制值

    0 | 000 | 11010101111000100101101100111101 | 3588381501
    1 | 001 | 11010101111000100101101100111100 | 3588381500
    2 | 010 | 11010101111000100101101100111111 | 3588381503
    3 | 011 | 11010101111000100101101100111110 | 3588381502
    4 | 100 | 11010101111000100101101100111001 | 3588381497
    

为了反转生成的密钥并确定原始值,您只需要使用相同的基本密钥执行相同的XOR操作。假设我们想获得第二行的原始值:

    11010101111000100101101100111101 XOR
    11010101111000100101101100111100 =
    00000000000000000000000000000001

这确实是你原来的价值。

现在,Stefan提出了非常好的观点,第一个主题至关重要。

为了掩盖他的顾虑,你可以保留最后一个,比方说8字节为纯随机垃圾(我认为称为nonce),你在加密原始ID时会生成,而忽略扭转它。这将大大增加您的安全性,代价是所有可能的正整数,32位(16,777,216而不是4,294,967,296,或1/256)。

这样做的类看起来像这样:

public static class int32crypto
{
    // C# follows ECMA 334v4, so Integer Literals have only two possible forms -
    // decimal and hexadecimal.
    // Original key:               0b11010101111000100101101100111101
    public static long baseKey = 0xD5E25B3D;

    public static long encrypt(long value)
    {
        // First we will extract from our baseKey the bits we'll actually use.
        // We do this with an AND mask, indicating the bits to extract.
        // Remember, we'll ignore the first 8. So the mask must look like this:
        // Significance mask:      0b00000000111111111111111111111111
        long _sigMask = 0x00FFFFFF;

        // sigKey is our baseKey with only the indicated bits still true.
        long _sigKey = _sigMask & baseKey;

        // nonce generation. First security issue, since Random()
        // is time-based on its first iteration. But that's OK for the sake
        // of explanation, and safe for most circunstances.
        // The bits it will occupy are the first eight, like this:
        // OriginalNonce:          0b000000000000000000000000NNNNNNNN
        long _tempNonce = new Random().Next(255);

        // We now shift them to the last byte, like this:
        // finalNonce:             0bNNNNNNNN000000000000000000000000
        _tempNonce = _tempNonce << 0x18;

        // And now we mix both Nonce and sigKey, 'poisoning' the original
        // key, like this:

        long _finalKey = _tempNonce | _sigKey;

        // Phew! Now we apply the final key to the value, and return
        // the encrypted value.

        return _finalKey ^ value;


    }

    public static long decrypt(long value)
    {
        // This is easier than encrypting. We will just ignore the bits
        // we know are used by our nonce.
        long _sigMask = 0x00FFFFFF;
        long _sigKey = _sigMask & baseKey;

        // We will do the same to the informed value:
        long _trueValue = _sigMask & value;

        // Now we decode and return the value:
        return _sigKey ^ _trueValue;

    }

}

答案 3 :(得分:2)

也许想法可能来自军事?在这些块中分组发票:
第28步兵师
- 第1旅 ---第一个国阵 ---- A A
---- B Co ---第二个国阵 ---- A A
---- B Co --2旅。
---第一个国阵 ---- A A
---- B Co ---第二个国阵 ---- A A
---- B Co --3rd Brigade
---第一个国阵 ---- A A
---- B Co ---第二个国阵 ---- A A
---- B Co http://boards.straightdope.com/sdmb/showthread.php?t=432978
组不必是顺序的,但组中的数字不是

<强>更新

在上面考虑按地点,时间,人等区分的群体。例如:使用卖家临时身份证创建群组,每10天更改一次或办公室/商店。

还有另外一个想法,你可能会说有点奇怪,但......当我想到它时,我越来越喜欢它。为什么不倒计算这些发票? 选择一个大号并倒计时。在计算时很容易跟踪项目数量,但倒计时?怎么会有人猜到哪里是一个起点?它很容易实现,  太。

答案 4 :(得分:1)

您可以从下面的代码中看到我使用newsequentialid()生成序列号,然后将其转换为[bigint]。由于这会产生4294967296的一致增量,我只需将该数字除以表中的[id](它可以是以纳秒或类似的方式播种的rand())。结果是一个总是小于4294967296的数字,所以我可以安全地添加它,并确保我不会重叠下一个数字的范围。

和平 兆

declare @generator as table ( 
[id] [bigint], 
[guid] [uniqueidentifier] default( newsequentialid()) not null, 
[converted] as (convert([bigint], convert ([varbinary](8), [guid], 1))) + 10000000000000000000,
[converted_with_randomizer] as (convert([bigint], convert ([varbinary](8), [guid], 1))) + 10000000000000000000 + cast((4294967296 / [id]) as [bigint])
);

insert into @generator ([id])
values      (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);

select [id],
   [guid],
   [converted],
   [converted] - lag([converted],
                     1.0)
                     over (
                         order by [id])                 as [orderly_increment],
   [converted_with_randomizer],
   [converted_with_randomizer] - lag([converted_with_randomizer],
                                     1.0)
                                     over (
                                         order by [id]) as [disorderly_increment]
from   @generator
order  by [converted]; 

答案 5 :(得分:1)

我不知道您在发票ID上设置的规则的原因,但您可以考虑使用内部发票ID,该发票ID可以是连续的32位整数,也可以是您可以与客户共享的外部发票ID

这样,您的内部ID可以从1开始,您可以每次添加一个,客户发票ID可以是您想要的。

答案 6 :(得分:1)

如果订单一直放在收件箱中,直到每个人在每天早上处理这些订单,看到在他创建我的发票之前将该人带到16:00之前,我会觉得他一直很忙。获得9:01发票让我觉得我是今天唯一的客户。

但是如果你在下订单时生成了ID,时间戳就什么也没告诉我。

我认为我实际上喜欢时间戳,假设两个客户同时需要创建ID的冲突很少。

答案 7 :(得分:0)

我认为Na Na有一个正确的想法,选择一个大数字和倒计时。从大值种子开始,向上或向下计数,但不要从最后一个占位符开始。如果您使用其他占位符之一,它会给出更高发票数量的错觉....如果他们实际上正在查看它。

这里唯一需要注意的是定期修改数字的最后X位数,以保持变化的外观。

答案 8 :(得分:0)

为什么不采用像

那样简单易读的数字
  • 前12位是yyyymmddhhmm格式的日期时间(确保发票ID的顺序)
  • 最后x位数是订单号(在本例中为8位数)

您获得的数字类似于20130814140300000008

然后像前12位数一样进行一些简单的计算

(201308141403) * 3 = 603924424209

第二部分(原文:00000008)可以这样混淆:

(10001234 - 00000008 * 256) * (minutes + 2) = 49995930  

很容易将其翻译成易读的数字,但除非您不知道客户如何根本不知道。

总而言之这个数字看起来像 603924424209-49995930 发票于2013年8月14日14:03发票,内部发票号为00000008。

答案 9 :(得分:0)

你可以编写自己的函数,当应用于前一个数字时,会生成下一个顺序随机数,它大于前一个但是随机的。虽然可以生成的数字来自有限集(例如,1到2次幂31之间的整数),但最终可能会重复,尽管不太可能。要添加更多复杂性到生成的数字,您可以在末尾添加一些AlphaNumeric字符。你可以在这里阅读Sequential Random Numbers

示例生成器可以是

private static string GetNextnumber(int currentNumber)
{
      Int32 nextnumber = currentNumber + (currentNumber % 3) + 5;
      Random _random = new Random();
      //you can skip the below 2 lines if you don't want alpha numeric
      int num = _random.Next(0, 26); // Zero to 25
      char let = (char)('a' + num);
      return nextnumber + let.ToString();
}

你可以像

一样打电话
string nextnumber = GetNextnumber(yourpreviouslyGeneratedNumber);