关于生成无法追踪的发票ID的想法
我想在我的应用中为客户打印发票。 每张发票都有一个发票ID 。 我希望ID为:
- 顺序(最近输入的ID迟到)
- 32位整数
- 不像1 2 3那样容易追踪,所以人们无法分辨出我们销售的商品数量。
我自己的想法:自特定日期和时间以来的秒数 (例如1/1/2010 00 AM)。
任何其他想法如何生成这些数字?
我不喜欢使用时间的想法。 您可以遇到各种各样的问题 – 时间差异,一秒钟内发生的几个事件等等。
如果您想要一些顺序且不易追踪的东西,那么如何为每个新Id生成1和任何您想要的随机数(例如100)。 每个新Id都是先前的Id +随机数。
您还可以为ID添加常量,使其看起来更令人印象深刻。 例如,您可以为所有ID添加44323,并将ID 15,23和27转换为44338,44346和44350。
你的问题有两个问题。 一个是可解决的,一个不是(有你给出的约束)。
可解决的:不可思议的数字
第一个很简单:当客户有权访问一组有效的发票号时,客户很难猜出有效的发票号(或下一个有效的发票号)。
您可以使用约束来解决此问题:
将发票号码分为两部分:
- 一个20位前缀,取自一系列递增的数字(例如自然数0,1,2,……)
- 随机生成的10位后缀
使用这些方案,有100万有效发票号码。 您可以预先计算它们并将它们存储在数据库中。 出示发票号时,请检查它是否在您的数据库中。 如果不是,则无效。
使用SQL序列分发数字。 发出新的(即未使用的)发票号时,增加seuqnce并从预先计算的列表中发出第n个数字(按值排序)。
不可解决:猜测客户数量
如果您想要阻止拥有多个有效发票号的客户猜测您已发出多少发票号(以及您拥有多少客户):这是不可能的。
你有一个所谓的“ 德国坦克问题 ”的变种。 在第二次世界大战中,盟国使用印在德国坦克齿轮箱上的序列号来猜测德国制造了多少坦克。 这很有效,因为序列号没有间隙地增加。
但即使你增加了数量,但德国坦克问题的解决方案仍然有效。 这很容易:
- 您可以使用此处描述的方法猜测最高发票数
- 您猜测两个连续发票号码之间的平均差异,并将数字除以该值
- 您可以使用线性回归来获得稳定的delta值(如果存在)。
现在,您可以很好地猜测发票数量的数量级(200,15000,五十万等)。
只要那里(理论上)存在两个连续发票号的平均值,这就可以正常工作。 即使使用随机数发生器,通常也是如此,因为大多数随机数发生器被设计成具有这样的平均值。
有一个对策:您必须确保两个连续数字的间隙不存在平均值。 具有此属性的随机数生成器可以非常容易地构造。
例:
- 从最后一个发票编号加上一个作为当前编号
- 将当前数字乘以随机数> = 2。 这是您当前的新号码。
- 获取一个随机位:如果该位为0,则结果为您当前的数字。 否则返回第2步。
虽然这在理论上是有效的,但很快就会耗尽32位整数。
我不认为这个问题有实际的解决方案。 两个连续数字之间的差距具有平均值(差异很小),您可以轻松猜出已发布数字的数量。 或者你将很快耗尽32位数。
Snakeoil(非工作解决方案)
不要使用任何基于时间的解决方案。 时间戳通常很容易猜到(可能会在发票上的某处打印一个大致正确的时间戳)。 使用时间戳通常会使攻击者更容易,而不是更难。
不要使用不安全的随机数。 大多数随机数生成器不具有加密安全性。 它们通常具有适用于统计数据但对您的安全性有害的数学属性(例如,可预测的分布,稳定的平均值等)
一种解决方案可能涉及异或(XOR)二进制位图。 结果函数是可逆的 , 可以生成非连续数字 (如果最低有效字节的第一位设置为1),并且非常容易实现。 而且,只要您使用可靠的序列生成器(例如,您的数据库),就不需要线程安全问题。
根据MSDN的说法 ,“异或运算的结果”是真的,当且仅当其中一个操作数为真时。 反向逻辑表示相等的操作数总是会导致错误。
举个例子,我刚在Random.org上生成了一个32位序列。 就是这个:
11010101111000100101101100111101
此二进制数转换为十进制的3588381501 ,hex的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
这确实是你原来的价值。
现在, 斯特凡提出了非常好的观点,第一个话题至关重要。
为了掩盖他的顾虑,你可以保留最后一个,比方说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; } }
也许想法可能来自军事? 在这些块中分组发票:
第28步兵师
– 第1旅
—第一国阵
—-一个有限公司
—- B公司
—第二国阵
—-一个有限公司
—- B公司
–2旅
—第一国阵
—-一个有限公司
—- B公司
—第二国阵
—-一个有限公司
—- B公司
– 第3旅
—第一国阵
—-一个有限公司
—- B公司
—第二国阵
—-一个有限公司
—- B公司
http://boards.straightdope.com/sdmb/showthread.php?t=432978
组不必是顺序的,而是组中的数字
UPDATE
在上面考虑由地点,时间,人等区分的群体。例如:使用卖家临时ID创建群组,每10天更改一次或通过办公室/商店。
还有另外一个想法,你可能会说有点奇怪,但……当我想到它时,我越来越喜欢它。 为什么不倒计算这些发票? 选择一个大数字并倒计时 。 在计算时很容易跟踪项目数量,但倒计时? 怎么会有人猜到哪里是一个起点? 它也很容易实现。
您可以从下面的代码中看到我使用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];
我不知道您在发票ID上设置的规则的原因,但您可以考虑使用内部发票ID,该发票ID可以是连续的32位整数,也可以是您可以与客户共享的外部发票ID。
这样,您的内部ID可以从1开始,您可以每次添加一个,客户发票ID可以是您想要的。
如果订单位于收件箱中,直到一个人每天早上处理这些订单,看到在他开始创建我的发票之前将该人带到16:00将给我一个他一直很忙的印象。 获得9:01发票让我觉得我是今天唯一的客户。
但是如果你在下订单时生成了ID,时间戳就什么也没告诉我。
因此,我认为我实际上喜欢时间戳,假设两个客户同时需要创建ID的冲突很少。
我认为娜娜有一个正确的想法,选择一个大数字和倒计时。 从大值种子开始,向上或向下计数,但不要从最后一个占位符开始。 如果您使用其他占位符之一,它会给出更高发票数量的错觉….如果他们实际上正在查看它。
这里唯一需要注意的是定期修改数字的最后X位数以保持变化的外观。
为什么不采用简单易读的数字构造
- 前12位是yyyymmddhhmm格式的日期时间(确保发票ID的顺序)
- 最后x位是订单号(在本例中为8位)
你得到的数字是20130814140300000008
然后像前12位数一样进行一些简单的计算
(201308141403) * 3 = 603924424209
第二部分(原始:00000008)可以像这样混淆:
(10001234 - 00000008 * 256) * (minutes + 2) = 49995930
很容易将其翻译成易读的数字,但除非您不知道客户如何根本不知道。
总而言之 ,这个号码在2013年8月14日14:03的发票上看起来像603924424209-49995930 ,内部发票号为00000008。
您可以编写自己的函数 ,当应用于前一个数字时,会生成下一个序列随机数 ,该序列随机数大于前一个随机数。 虽然可以生成的数字来自有限集(例如,1到2次幂的整数31),但最终可能会重复,尽管不太可能。 要为生成的数字添加更多复杂性 , 您可以在末尾添加一些AlphaNumeric字符 。 您可以在此处阅读顺序随机数 。
示例生成器可以是
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);