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

Ehs*_*n88 10 c# sql integer numbers data-structures

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

  • 顺序(最近输入的ID迟到)
  • 32位整数
  • 像1 2 3那样不容易追溯,所以人们无法分辨出我们销售的商品数量.

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

任何其他想法如何生成这些数字?

Vad*_*dim 10

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

如果你想要一些顺序且不容易追踪的东西,那么如何为每个新Id生成1和任何你想要的随机数(例如100).每个新Id都是先前的Id +随机数.

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


ste*_*hke 7

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

可解决的:不可思议的数字

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

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

将发票编号分为两部分:

  1. 一个20位前缀,取自一系列递增的数字(例如自然数0,1,2,......)
  2. 随机生成的10位后缀

有了这些计划,有100万有效发票号码.您可以预先计算它们并将它们存储在数据库中.显示发票号时,请检查它是否在您的数据库中.如果不是,则无效.

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

不可解决:猜测客户数量

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

你有一个所谓的" 德国坦克问题 " 的变种.在第二次世界大战中,盟国使用印在德国坦克齿轮箱上的序列号来猜测德国制造了多少坦克.这很有效,因为序列号没有间隙地增加.

但即使你增加了数量,但德国坦克问题的解决方案仍然有效.这很容易:

  1. 您可以使用此处描述的方法猜测最高发票数
  2. 您猜测两个连续发票号码之间的平均差异,并将数字除以此值
  3. 您可以使用线性回归来获得稳定的delta值(如果存在).

现在您可以很好地猜测发票数量的数量级(200,15000,50万等).

只要在理论上存在两个连续发票号的平均值,这就可以正常工作.即使使用随机数发生器,通常也是如此,因为大多数随机数发生器被设计成具有这样的平均值.

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

例:

  1. 从最后一个发票编号加上一个作为当前编号
  2. 将当前数字乘以随机数> = 2.这是您当前的新号码.
  3. 获取一个随机位:如果该位为0,则结果为您当前的数字.否则返回第2步.

虽然这在理论上有效,但很快就会耗尽32位整数.

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

Snakeoil(非工作解决方案)

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

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


Ono*_*dai 5

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

根据MSDN,'当或仅当其操作数中的一个为真时,[异或运算]的结果才为真.反向逻辑表明,相等的操作数总是会导致错误.

例如,我刚在Random.org上生成了一个32位序列.就是这个:

11010101111000100101101100111101
Run Code Online (Sandbox Code Playgroud)

这个二进制数转换为3588381501十进制,0xD5E25B3D十六进制.我们称之为你的基础密钥.

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

    public static long FlipMask(long baseKey, long ID)
    {
        return baseKey ^ ID;
    }
Run Code Online (Sandbox Code Playgroud)

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

  • ID
  • ID的二进制表示
  • XOR运算后的二进制值
  • 最后,'加密'十进制值

    0 | 000 | 11010101111000100101101100111101 | 3588381501
    1 | 001 | 11010101111000100101101100111100 | 3588381500
    2 | 010 | 11010101111000100101101100111111 | 3588381503
    3 | 011 | 11010101111000100101101100111110 | 3588381502
    4 | 100 | 11010101111000100101101100111001 | 3588381497
    
    Run Code Online (Sandbox Code Playgroud)

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

    11010101111000100101101100111101 XOR
    11010101111000100101101100111100 =
    00000000000000000000000000000001
Run Code Online (Sandbox Code Playgroud)

这确实是你原来的价值.

现在,斯特凡提出了非常好的观点,第一个话题至关重要.

为了掩盖他的顾虑,你可以保留最后一个,比方说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;

    }

}
Run Code Online (Sandbox Code Playgroud)