我想在我的应用中为客户打印发票。每张发票都有发票ID 。我希望ID为:
我自己的想法: 自特定日期&以来秒的数量时间(例如1/1/2010 00 AM)。
如何生成这些数字?
答案 0 :(得分:10)
我不喜欢使用时间的想法。你可以遇到各种各样的问题 - 时间差异,一秒钟内发生的几个事件等等。
如果您想要一些顺序且不易追踪的内容,那么如何为每个新Id生成1和任意之间的随机数(例如100)。每个新ID都是前一个Id +随机数。
您还可以为ID添加常量,使其看起来更令人印象深刻。例如,您可以为所有ID添加44323,并将ID 15,23和27转换为44338,44346和44350。
答案 1 :(得分:7)
您的问题有两个问题。一个是可解决的,一个不是(有你给出的约束)。
第一个很简单:当客户有权访问一组有效的发票号时,客户很难猜出有效的发票号(或下一个有效的发票号)。
您可以使用约束来解决此问题:
将发票编号分为两部分:
使用这些方案,有100万有效发票号码。您可以预先计算它们并将它们存储在数据库中。显示发票号时,请检查它是否在您的数据库中。如果不是,则无效。
使用SQL序列分发号码。当发出新的(即未使用的)发票号时,增加seuqnce并从预先计算的列表中发出第n个数字(按值排序)。
如果您想阻止拥有多个有效发票号码的客户猜测您已发出多少发票号码(以及您拥有多少客户):这是不可能的。
你有一个所谓的“German tank problem”的变体形式。在第二次世界大战中,盟国使用印在德国坦克齿轮箱上的序列号来猜测德国制造了多少坦克。这很有效,因为序列号没有间隙地增加。
但即使你用差距增加数字,德国坦克问题的解决方案仍然有效。这很容易:
现在你对发票数量的数量级有了很好的猜测(200,15000,50万等)。
这可以在那里(理论上)存在两个连续发票号的平均值。即使使用随机数发生器,通常也是如此,因为大多数随机数发生器被设计成具有这样的平均值。
有一个对策:您必须确保两个连续数字的间隙不存在平均值。具有此属性的随机数生成器可以非常容易地构建。
示例:
虽然这在理论上有效,但你很快就会用完32位整数。
我认为这个问题没有实用的解决方案。两个连续数字之间的间隙具有平均值(方差很小),您可以轻松猜出已发布数字的数量。或者你将很快耗尽32位数。
不要使用任何基于时间的解决方案。时间戳通常很容易猜到(可能会在发票上的某处打印一个大致正确的时间戳)。使用时间戳通常会使攻击者更容易,而不是更难。
不要使用不安全的随机数。大多数随机数生成器不具有加密安全性。它们通常具有对统计有利但对您的安全性有害的数学属性(例如可预测的分布,稳定的平均值等)。
答案 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;
}
以下列表包含一些生成的内容。其栏目如下:
最终,'加密'十进制值
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)
<强>更新强>
在上面考虑按地点,时间,人等区分的群体。例如:使用卖家临时身份证创建群组,每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)
为什么不采用像
那样简单易读的数字您获得的数字类似于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);