18位唯一标识符 - 代码可靠性

6

我想要一个永远唯一的数字,我写了下面的代码,它会生成一个数字并在末尾添加一个校验位,我想知道这个代码有多可靠?

public void GenerateUniqueNumber(out string ValidUniqueNumber) {
        string GeneratedUniqueNumber = "";

        // Default implementation of UNIX time of the current UTC time
        TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
        string FormatedDateTime = Convert.ToInt64(ts.TotalSeconds).ToString();
        string ssUniqueId = DateTime.UtcNow.ToString("fffffff");
        //Add Padding to UniqueId
        string FormatedUniqueId = ssUniqueId.PadLeft(7, '0'); 

        if (FormatedDateTime.Length == 10 && FormatedUniqueId.Length == 7)
        {
            // Calculate checksum number using Luhn's algorithm.
            int sum = 0;
            bool odd = true;
            string InputData = FormatedDateTime + FormatedUniqueId;
            int CheckSumNumber;

            for (int i = InputData.Length - 1; i >= 0; i--)
            {
                if (odd == true)
                {
                    int tSum = Convert.ToInt32(InputData[i].ToString()) * 2;
                    if (tSum >= 10)
                    {
                        string tData = tSum.ToString();
                        tSum = Convert.ToInt32(tData[0].ToString()) + Convert.ToInt32(tData[1].ToString());
                    }
                    sum += tSum;
                }
                else
                    sum += Convert.ToInt32(InputData[i].ToString());
                odd = !odd;
            }
            //CheckSumNumber = (((sum / 10) + 1) * 10) - sum;
            CheckSumNumber = (((sum + 9) / 10) * 10) - sum;

            // Compute Full length 18 digit UniqueNumber
            GeneratedUniqueNumber = FormatedDateTime + FormatedUniqueId + Convert.ToString(CheckSumNumber);
        }
        else
        {
            // Error
            GeneratedUniqueNumber = Convert.ToString(-1);
        }

        ValidUniqueNumber = GeneratedUniqueNumber;        
    }

编辑:澄清 GUID 无法使用,需要通过电话键盘将数字输入 IVR 系统中。


你能给我们更多关于你使用这个的上下文细节吗?例如,为什么你只想要数字(而 Guid 本来就是数字)? - Matthew
它需要输入到IVR系统中,因此不能使用GUID。另外,要求号码最多只能有18位数字。 - 001
@ Matthew,是的,IVR会提示用户使用手机或电话键盘将号码输入系统,然后它会验证该号码。 - 001
1
“Unique”在什么上下文中?生成的数字是否在一个中央位置(如果是,则可以使用简单的整数递增生成每个数字),还是必须在多个独立设备上动态生成?每个设备是否都有可用作ID一部分的唯一标识符? - jalf
@Khou - 如果值可以是连续的,那就完全不同了。然后你可以创建一个带有 bigint 身份列和检查约束的表来限制该值。 - Thomas
显示剩余4条评论
9个回答

6
你不能使用 GUID,但是你可以创建类似于 GUID 的独特编号格式,该格式基于计算机的 MAC 地址(空格)和当前时间及日期(时间)。如果所有机器都有同步时钟,则这可保证唯一性。
更多信息,请查看 这里

5
为什么不直接使用 Guid

3
Guid只是一个128位的数字,其中的“字母”仅是该数字的十六进制表示方式——以这种方式阅读更加容易。 - Mark H
2
但是如果他的限制是十进制格式的18位数字,那么GUID会太大了,对吧? - CodingInsomnia
9
没必要嘲笑他。如果他已经知道一切,就不需要提出问题了。请查看FAQ中的“友善待人”部分:http://stackoverflow.com/faq - Matthew
2
@Matthew:如果你开始削减GUID的位数,它就不再是全局唯一的了。那是一个非常非常糟糕的想法。 - jalf
2
@Matthew,GUID不是随机的。它们是全局唯一的,因为它们包含一个MAC地址(唯一的、全球管理)和一个准确的高分辨率时间戳。因此,它们是时空坐标,如果我们没有MAC地址冲突,它们是不可能发生碰撞的。请参见http://blogs.msdn.com/b/oldnewthing/archive/2008/06/27/8659071.aspx以获取解释。 - Fahad Sadah
显示剩余11条评论

4

这种方法存在一些问题:

  • 你只是从1970年1月1日开始计算毫秒数。你可以通过将ts.TotalSeconds四舍五入到0.0000001来获取此信息。所有的转换和毫秒计算都是不必要的。

  • 十年大约是3×10¹¹毫秒。你保留了17位有效数字,因此在接下来的十年中,前5位数字永远不会改变,并且不能用于区分数字。它们是无用的。

  • 你正在生成1970年至今的毫秒数吗?如果不是,它们也不能用于区分数字,也是无用的。

  • 这完全取决于返回日期的机器。任何有权访问此机器的人都可以生成他们想要的“唯一”数字。这是个问题吗?

  • 任何看到这些数字的人都可以知道它是何时生成的。这是个问题吗?

  • 任何人都可以预测什么时间会生成哪个数字。这是个问题吗?

  • 1015 毫秒大约是30000年。之后,你的算法将重复数字。看起来很长,但你指定了“永远”,而30000年不是“永远”。你真的是指“永远”吗?


我刚注意到你需要TotalSeconds中数字的位数等于10。这将在32年后失效,因此您的前7个数字是无用的。如果您必须截断为10个数字,则应该进行模运算10000000000。 - Dour High Arch
这完全取决于返回日期的机器。任何有权访问该机器的人都可以生成他们想要的任何“唯一”数字。这是一个问题吗? <-- 不是问题任何人看到这些数字都可以知道它是何时生成的。这是一个问题吗? <-- 不是问题任何人都可以预测什么数字将在何时生成。这是一个问题吗? <-- 不是问题 - 001
@Khou,打错了!实际上是300年:http://www.wolframalpha.com/input/?i=10000000000+seconds 不过仍然应该使用模运算。 - Dour High Arch

3

如果我正确理解你的实现,它仅使用当前日期/时间作为基础。这意味着如果您同时创建两个ID,它们将不是唯一的。


1

由于您在评论中提到ID存储在数据库中,因此您可以使用您提到的方法或随机生成ID并检查其是否存在于数据库中来生成ID。

如果它已经存在,则生成一个新的ID,否则您就完成了。

不过,我建议确保检查ID的存在和将记录保存到数据库中的实际操作在事务中完成,否则您可能会面临另一个请求在检查ID和创建行之间创建该记录的风险。

还有一个问题,为什么不使用数据库自动生成的自增数字?数据库将保证其在该表中的唯一性。


1

您没有说明这些数字将用于什么。它们是否与某种价值相关联?如果用户能够猜出方案并猜测有效的票号,这会成为一个问题吗?

如果这些数字很难被猜到很重要,那么这个方案就不可行了;最好使用输出看起来非常随机的数据。您可以采用单调递增的序列号,并使用块密码(具有64位块大小)进行加密;这给您提供了64位输出或约20个十进制数字,您可以取其中的最后18个。(如果可逆性很重要,即给定一个票号,您希望能够恢复序列号,则在此处需要更加小心。)

您需要铸铁般的100%保证,以确保没有两个票号是相同的吗?如果是这样,您需要将它们保存在数据库中,并在使用时标记它们。如果这样做,每次只需使用良好的随机数生成器并检查重复项即可。


1

使用系统时间是一个不错的开始,但如果您需要在同一时间生成两个唯一标识符(UID),这种方法会导致冲突。而且,您使用的“fffffff”格式并没有起到太大作用:Windows时钟分辨率只有15-16毫秒,所以其中的“f”只有一两个是有效的。

此外,您的方法会准确地告诉您ID生成的时间。根据您的需求,这可能是一个可取的特性,也可能是一个安全风险。

您需要让您的ID包含其他信息,而不仅仅是时间。以下是一些可能的选择:

  • 一个随机数
  • 一个循环计数器
  • 程序名称的哈希值(如果您需要在多个程序中使用这些ID)
  • MAC地址或其他机器标识符(如果这些ID需要在多台计算机之间保持唯一)

如果您想要确保唯一性,那么请将您的ID存储在数据库中,以便可以检查重复项。


0

正如“Andrew Hare”所说,您可以使用Guid。 关于您的代码,答案是“不行”!因为如果客户端计算机的日期时间错误或更改,结果可能会有几个或更多!


-1

其实也没有所谓的随机。以下是一个建议。

  1. 创建自己的“随机”18位数字
  2. 在将其发送给用户之前,检查它是否已存在于数据库中
  3. 如果已经存在于数据库中,则重复以上步骤。

天啊,没有评论的踩,接下来会是什么?;-) 这里不应该使用时间戳,而且假设你在编写并发代码。数据库检查保证没有重复;我有错吗?生成数字的方式取决于您情况下的安全需求。您的问题陈述了您希望这个数字“永久唯一”,这解决了它 :-) - IrishChieftain
随机和唯一不是同一回事。试图使用随机数字生成唯一标识符是一个极其糟糕的想法。即使假设您检查了以前使用过的数字的数据库(问题中也没有提到这样的数据库存在或者在他的环境中是否是可能的),仍然存在着非停机算法的理论风险和严重的性能瓶颈风险。人们需要停止在关于唯一标识符的问题的答案中使用“随机”一词。如果你有一个数据库,计数器会更有意义。 - Aaronaught
Khou在他的原始问题的评论部分指出了一个DB。 - IrishChieftain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接