关于生成无法跟踪的发票ID的想法
本文关键字:ID 跟踪 于生成 | 更新日期: 2023-09-27 18:27:02
我想在我的应用程序中为客户打印发票。每张发票都有一个发票ID。我希望ID为:
- 顺序(最近输入的ID来得晚)
- 32位整数
- 不容易像1 2 3那样追踪,这样人们就无法知道我们卖了多少商品
我自己的想法:自特定日期以来的秒数;时间(例如,2010年1月1日上午00时)。
如何生成这些数字还有其他想法吗?
我不喜欢使用时间的想法。你可能会遇到各种各样的问题——时差、一秒钟内发生的几个事件等等
如果你想要一些顺序的、不容易追踪的东西,那么为每个新的Id生成一个介于1和你想要的任意值之间的随机数(例如100)怎么样?每个新Id都将是以前的Id+随机数。
您还可以在ID中添加一个常量,使其看起来更令人印象深刻。例如,您可以将44323添加到所有ID,并将ID 15、23和27转换为44338、44346和44350。
您的问题有两个问题。一个是可解的,另一个是不可解的(根据你给出的约束)。
可解:不可解的数字
第一个很简单:当客户可以访问一组有效的发票号码时,客户应该很难猜测一个有效的发票编号(或下一个有效发票编号)。
你可以用你的约束来解决这个问题:
将您的发票号码分成两部分:
- 20位前缀,取自一系列递增的数字(例如自然数0,1,2,…)
- 随机生成的10位后缀
有了这些方案,大约有100万个有效的发票号码。您可以预先计算它们并将它们存储在数据库中。当收到发票号码时,请检查它是否在您的数据库中。如果不是,则无效。
使用SQL序列来分配数字。当开具新的(即未使用的)发票编号时,增加seqance并从预先计算的列表中开具第n个编号(按值排序)。
无法解决:猜测客户数量
当你想阻止拥有多个有效发票号码的客户猜测你已经开具了多少发票号码(以及你有多少客户)时:这是不可能的。
你有一个变种,所谓的"德国坦克问题"。在第二次世界大战中,盟军使用印在德国坦克变速箱上的序列号来猜测德国生产了多少坦克。这是有效的,因为序列号在不断增加,没有间隙。
但即使你增加了缺口的数量,德国坦克问题的解决方案仍然有效。这很容易:
- 您可以使用此处描述的方法来猜测最高的已开具发票编号
- 你猜出两个连续发票号码之间的平均差,然后用这个值除以这个数字
- 您可以使用线性回归来获得一个稳定的delta值(如果存在的话)
现在你已经很好地猜测了发票数量的数量级(200、15000、50万等)
只要(理论上)存在两个连续发票号的平均值,这就有效。即使使用随机数生成器,情况通常也是如此,因为大多数随机数生成器都设计为具有这样的平均值。
有一个反措施:你必须确保两个连续数字的差距不存在平均值。具有这种性质的随机数生成器可以非常容易地构造。
示例:
- 从上一个发票编号加上一作为当前编号开始
- 将当前数字乘以一个>=2的随机数。这是您当前的新号码
- 获取一个随机位:如果位为0,则结果为当前数字。否则返回步骤2
虽然这在理论上可行,但很快就会用完32位整数。
我认为这个问题没有切实可行的解决办法。两个连续数字之间的差距都有一个平均值(方差很小),你可以很容易地猜测发布数字的数量。否则,您将很快用完32位数字。
蛇油(非工作溶液)
不要使用任何基于时间的解决方案。时间戳通常很容易猜测(发票上可能会打印出大致正确的时间戳)。使用时间戳通常会让攻击者更容易,而不是更难。
不要使用不安全的随机数。大多数随机数生成器在加密方面并不安全。它们通常具有有利于统计但不利于安全的数学属性(例如可预测分布、稳定平均值等)
一个解决方案可能涉及异或(XOR)二进制位图。结果函数是可逆的,可能生成非序列号(如果最低有效字节的第一位设置为1),并且非常容易实现。而且,只要使用可靠的序列生成器(例如数据库),就不需要考虑线程安全问题。
根据MSDN的说法,"[异或运算]的结果为真,当且仅当其操作数中有一个为真。"反向逻辑说相等的操作数总是会导致错误。
举个例子,我刚刚在Random.org上生成了一个32位序列
11010101111000100101101100111101
此二进制数以十进制形式翻译为3588381501,以十六进制形式翻译为0xD5E25B3D。让我们称之为您的基本密钥。
现在,让我们使用([base-key]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位(16777216而不是4294967296,或其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旅
---第一个BN
----A公司
----B Co
---第二个BN
----A公司
----B Co
--第2旅
---第一个BN
----A公司
----B Co
---第二个BN
----A公司
----B Co
--第3旅
---第一个BN
----A公司
----B Co
---第二个BN
----A公司
----B Co
http://boards.straightdope.com/sdmb/showthread.php?t=432978
分组不一定是连续的,但分组中的数字是
更新
将以上内容视为按地点、时间、人员等区分的组。例如:使用卖家临时ID创建组,每10天更改一次,或按办公室/商店更改一次。
还有另一个想法,你可能会说有点奇怪,但。。。当我想到它的时候,我越来越喜欢它。为什么不倒计时这些发票呢选择一个大数字并倒计时。向上计数时很容易追踪项目的数量,但向下计数呢?怎么会有人猜测起点在哪里?它易于实现,也
如果订单一直放在收件箱里,直到每天早上有一个人处理它们,看到那个人直到16:00才抽出时间创建我的发票,我会觉得他很忙。拿到9:01的发票让我觉得自己是今天唯一的顾客。
但如果您在我下订单时生成ID,时间戳告诉我任何信息。
因此,我认为我实际上喜欢时间戳,假设两个客户同时需要创建一个ID的冲突是罕见的。
您可以从下面的代码中看到,我使用newsequentialid()生成一个序列号,然后将其转换为[bigint]。由于这会产生4294967296的一致增量,我只需将该数字除以表上的[id](它可以是以纳秒或类似的值为种子的rand())。结果是一个总是小于4294967296的数字,所以我可以安全地添加它,并确保我不会与下一个数字的范围重叠。
和平Katherine
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,它可以是连续的32位整数,还有一个外部发票ID,您可以与客户共享。
这样,您的内部Id可以从1开始,每次都可以添加一个,客户发票Id可以是您想要的。
我认为Na Na选择一个大数字并倒计时的想法是正确的。从一个大值种子开始,向上或向下计数,但不要从最后一个占位符开始。如果你使用其他占位符之一,它会给人一种发票计数更高的错觉。。。。如果他们真的在看的话。
这里唯一需要注意的是定期修改数字的最后X位,以保持更改的外观。
为什么不采用像一样构造的易读数字
- 前12位是yyyymmdhhmm格式的日期时间(确保发票ID的顺序)
- 最后x位是订单号(在本例中为8位)
然后你得到的数字大约是201308140300000008
然后用它做一些简单的计算,比如前12位
(201308141403) * 3 = 603924424209
第二部分(原始:00000008)可以像这样混淆:
(10001234 - 00000008 * 256) * (minutes + 2) = 49995930
把它翻译回一个易读的数字很容易,但除非你不知道客户是怎么一点线索都没有。
加在一起,这个数字看起来像603924424209-499959302013年8月14日14:03的发票,内部发票编号为00000008。
您可以编写自己的函数,当应用于前一个数字时,会生成下一个序列随机数,该随机数大于前一个,但是随机的。尽管可以生成的数字将来自有限集(例如,1到2的幂31之间的整数),并且最终可能会重复,尽管可能性很小。要为生成的数字添加更多复杂性,可以在末尾添加一些字母数字字符。你可以在这里读到序列随机数。
示例生成器可以是
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);