UUID有多独特?

696

使用UUID来唯一标识某些东西(我用它来上传到服务器的文件)有多安全?据我所知,它是基于随机数的。然而,我认为在足够长的时间内,仅凭纯粹的偶然性,它最终会重复自身。是否有更好的系统或某种类型的模式来缓解这个问题?


25
如果时间足够长,那么这个值就足够大了 :) - lutz
141
“UUID有多独特?” “我相信它是全球唯一的。” ;) - Miles
47
除非你计划在金星上开发,否则 GUID 应该就足够了。 - skaffman
27
“unique” 意味着“绝不会碰撞”。如果有任何可能会产生冲突,那么它就不是独一无二的。因此按照定义,UUID 不是独一无二的,只有在你准备好面对潜在碰撞并且不考虑发生碰撞的几率时才是安全的。否则,你的程序就是错误的。你可以将 UUID 称为“几乎唯一”,但这并不意味着它是“独一无二”的。 - eonil
10
UUID(通用唯一标识符)“在实际应用中”是唯一的,即使存在生成重复值的可能性非常小,除非生成的ID数量开始足够大以至于这种情况在统计学上变得显著,否则依赖于此的程序并不会出错。 - Nathan Griffiths
显示剩余5条评论
12个回答

692

非常安全:

据估计,一个人被陨石击中的年度风险是1/170亿,这意味着概率约为0.00000000006(6×10−11),相当于在一年内创建数万亿个UUID并且有一个重复的几率。换句话说,即使在未来100年内每秒生成10亿个UUID,仅有50%的概率会出现一个重复。

注意:

然而,这些概率只适用于使用足够熵量生成UUID的情况。否则,由于统计分散可能较低,重复的概率可能会显著提高。在需要为分布式应用程序提供唯一标识符的情况下,即使合并了来自许多设备的数据,UUID也不会发生冲突,每个设备上使用的种子和生成器的随机性必须可靠地维护整个应用程序的生命周期。如果这不可行,则RFC4122建议改用命名空间变体。

来源:维基百科关于通用唯一标识符的文章中{{link1:随机UUID重复概率部分}}(链接指向2016年12月修改前的版本)。

同时请参阅同一通用唯一标识符文章中关于该主题的{{link2:冲突}}部分。


49
我喜欢维基百科上的这一部分:“然而,这些概率只有在使用足够熵的情况下生成UUID时才成立。否则,由于统计离散度可能较低,重复的概率可能会显着增加。”因此,鉴于这句话,请问重复的实际机会是多少?我们无法在计算机上创建真正的随机数,对吗? - mans
7
实际上,已经投入了大量工作来寻找方法,尽可能地向随机数API引入尽可能多的熵(“真正的随机性”,我想您可以这样称呼它)。请参见http://en.wikipedia.org/wiki/Entropy_%28computing%29。 - broofa
8
“其实这种碰撞的概率比我想象中的要高。我猜是生日悖论。” - Cameron
3
@linus_hologram - 这并不是一个JS的问题。熵源通常在操作系统层面进行配置。 - Stephen C
我在这个问题上已经进行了工作,它生成了重复的内容。我不确定它是否有机会通过钥匙链共享值来生成重复内容,或者是因为UUID生成了重复内容。 [UICKeyChainStore setString:uuidString forKey:@"uuid"]; [defaults setObject:uuidString forKey:@"uuid"]; [defaults synchronize]; - undefined
显示剩余5条评论

216
如果“充足时间”是指100年,并且每秒钟创建10亿个,那么在100年后,你有50%的概率发生碰撞。

251
只有在使用了256艾字节的存储空间来存储这些ID之后才能进行。 - Bob Aman
53
有趣的事是,你可以生成两个完全相同的东西,当然需要超乎想象的巧合、运气和神的干预,尽管概率非常小,但仍有可能发生!:D 是的,这并不会发生。只是说为了让人们想一想,当你创建一个重复时会有什么有趣的瞬间!截屏视频! - scalabl3
7
唯一性纯粹因为随机性吗?还是存在其他因素?(例如时间戳、IP等) - Weishi Z
24
@TheTahaan 这并不是随机的意思。它并不意味着“完全不可预测”——通常它们会遵循某种分布。如果你抛10个硬币,得到2个正面,接着3个反面,再接着5个正面的几率是很低的(2^-10,约为0.001)。这是真正的随机,但我们绝对可以知道获得特定结果的机会。我们只是无法预先知道它是否会发生。 - Richard Rast
7
简而言之,这个实现所犯的错误在于使用了版本1的UUID,其唯一性依赖于时间戳和MAC地址的组合。但如果您生成UUID速度足够快,时间戳可能还没有增加。在这种情况下,UUID生成算法应跟踪上次使用的时间戳并将其递增1。他们显然未能执行此步骤。然而,在同一机器短时间内正确生成的所有版本1 UUID将展示明显的相似之处,但应仍是唯一的。 - Bob Aman
显示剩余5条评论

156

UUID有不止一种类型,所以“安全性”取决于您使用的 UUID 类型(UUID 规范称之为“版本”)。

  • 版本 1 是基于时间加 MAC 地址的 UUID。128 位包含了48位用于网络卡的MAC地址(由制造商唯一分配)和一个具有100纳秒分辨率的60位时钟。该时钟在 3603 年才会回绕,因此这些 UUID 至少安全可用至那时(除非您需要每秒超过1000万个新 UUID 或有人克隆了您的网络卡)。我说“至少”是因为时钟从 1582 年 10 月 15 日开始计时,因此时钟回绕后约400年内甚至都没有小概率重复的可能性。

  • 版本 4 是随机数 UUID。其中有六个固定位,其余 UUID 的 122 位是随机生成的。参见 维基百科或其他相关分析来了解有多么不可能出现重复的情况。

  • 版本 3 使用 MD5,版本 5 使用 SHA-1 来创建这 122 位,而不是使用随机或伪随机数生成器。因此就安全性而言,它就像版本 4 是一个统计问题(只要您确保摘要算法一直在处理唯一的内容)。

  • 版本 2 类似于版本 1,但时钟较小,因此会更快地回绕。但由于版本 2 UUID 用于 DCE,所以您不应该使用这些。

因此,对于所有实际问题来说它们都是安全的。如果您对概率 (例如,您是那种担心地球会在您有生之年被大型小行星撞毁的人) 不舒服,只需确保使用版本 1 的 UUID 就可以保证其唯一性(在您有生之年内,除非您打算活到公元 3603 年)。

那么为什么不是每个人都使用版本1 UUID呢?因为版本1 UUID会公开生成它的机器的MAC地址,而且它们可能是可预测的--这两个问题可能会对使用这些UUID的应用程序产生安全影响。


1
默认使用版本1 UUID 在同一台服务器上为许多人生成时存在严重问题。由于可以在任何语言或平台(包括JavaScript)中快速编写生成器,因此版本4 UUID 是我的默认选项。 - Justin Bozonier
1
@Hoylen 讲得很清楚!但是需要这么夸张吗? - Dinoop paloli
4
理论上来说,它是由制造商独特指定的。 - OrangeDog
17
您无需在一秒钟内生成1000万个版本1的UUID就会遇到重复;只需要在单个“tick”时间内生成16384个UUID批次即可导致序列号溢出。我曾经看到过这种情况发生在一个依赖于时钟源的实现上,该时钟源天真地具有微秒级精度,并且未保证单调性(系统时钟不是)。请小心使用UUID生成代码,并特别小心使用基于时间的UUID生成器。它们很难做到正确,因此在使用之前要进行负载测试。 - Mike Strobel

28

对此问题的答案可能在很大程度上取决于UUID的版本。

许多UUID生成器使用版本4的随机数。然而,其中许多使用伪随机数生成器(PRNG)来生成它们。

如果使用种子不足且周期较小的PRNG来生成UUID,我会说它并不是非常安全。一些随机数生成器也具有较差的方差,即更倾向于某些数字而不是其他数字。这样做效果不好。

因此,它的安全性取决于用于生成它的算法。

另一方面,如果您知道这些问题的答案,那么我认为版本4的UUID应该非常安全。事实上,我正在使用它来标识网络块文件系统中的块,并且到目前为止还没有发生冲突。

在我的情况下,我使用的PRNG是Mersenne Twister,我对其种子的方式非常小心,包括从多个来源包括/dev/urandom获取。Mersenne Twister的周期为2^19937-1。在我看到重复的UUID之前,需要非常非常长的时间。

因此,请选择一个好的库或自己生成,并确保使用一个体面的 PRNG 算法。


24

对于UUID4,我将其设置为大约有与一个边长为360,000km的立方体盒子中的沙粒数量相同的ID。这是一个边长约为木星直径2.5倍的盒子。

在工作时,需要有人告诉我是否弄错了单位:

  • 沙粒的体积为0.00947mm^3 (卫报)
  • UUID4有122位随机比特-> 5.3e36个可能的值(维基百科)
  • 那么多沙粒的体积=5.0191e34毫米^3或5.0191e+25m^3
  • 具有该体积的立方盒子的边长=3.69E8m或369,000km
  • 木星的直径:139,820km (谷歌)

3
实际上,我认为这假设了100%的装载率,所以我可能需要增加一个因素来考虑这点! - lost
11
这实际上非常有帮助,让我意识到可能没关系,并且有其他事情需要担心。哈哈 - wongz
假设盒子里装满了沙子。你需要明确所有的假设。 - user1623709
2
显然这是一个满箱子,否则我会说“一个大小为已知宇宙的0.0000000002%的盒子”(例如,未经计算!),这样描述不够准确。我认为装箱系数是上述计算中更大的问题,但至少它是保守的(即比100%更现实的值将使箱子更大)。 - lost

18
我同意其他回答。UUID对于几乎所有实际用途而言都足够安全1,当然也包括你的需求。
但是假设(假想)它们不安全,有没有更好的系统或某种模式来缓解这个问题呢?
以下是几种方法:
1. 使用更大的UUID。例如,不使用128位随机数,而要使用256位、512位等等。每增加一个位数,类型4样式UUID发生冲突的概率就会减少一半,前提是您拥有可靠的熵源2
2. 构建一个生成UUID并记录其发出的每个UUID的集中式或分布式服务。每次生成新的UUID时,它都会检查该UUID以前是否已被发出过。如果我们假设运行服务的人绝对值得信赖、不可腐败等等,那么这样的服务在技术上很容易实现(我想)。不幸的是,他们不是...尤其是在政府安全机构可能干扰的情况下。所以,这种方法可能在现实世界中不切实际,甚至是不可能的3

1- 如果UUID的唯一性决定了核导弹是否会袭击你国首都,很多你的同胞可能不会被“概率极低”的说法所说服。因此,我使用“几乎全部”这个限定词。
2- 这里有一个哲学问题:是否有任何事情是真正随机的?如果不是,我们怎么知道呢?我们所知道的宇宙是一个模拟吗?是否存在上帝,可能会调整物理规律以改变结果?
3- 如果有人知道这个问题的研究论文,请评论。


10
我想指出的是,方法2基本上背离了使用UUID的主要目的,这时你也可以使用传统的编号ID。 - Petr Vnenk
2
我不同意。顺序编号ID的缺陷在于它们太容易被猜测了。你应该能够以一种使UUID难以猜测的方式实现第二种方法。 - Stephen C
1
但是,即使对于您所说的内容,您基本上可以使用任何其他随机字符串/数字,并仅检查重复项,您没有任何理由使用UUID,而不是6个字符长的随机字符串。 - Petr Vnenk
2
嗯,是和不是。这取决于需要唯一标识符的上下文环境。如果它们只需要在一个封闭系统中保持唯一性,那么使用短随机字符串并将它们全部存储在数据库(或其他地方)中以检查重复是可行的。但这并不能保证您获得了通用的唯一性。如果系统生命周期内生成的唯一标识符数量足够多,则会遇到扩展问题,假设需要唯一标识符随时间而唯一…而不仅仅是在某个时间点上。 - Stephen C
为什么使用集中式数据库不能保证普遍唯一性?这对我来说毫无意义。 - Lance
如果有些参与者没有使用集中式数据库怎么办?如果数据库在(比如)20年后被重置怎么办?如果你用尽了ID怎么办?我的观点是,除非你对“普遍”的含义加以重要的限制,“普遍唯一性”的保证几乎不可能实现。请注意,这都是在一个问题的背景下提出的:“UUID有多独特?” - Stephen C

14

引用自维基百科

因此,任何人都可以创建UUID并将其用于标识某个东西,有合理的把握认为该标识符永远不会被他人意外地用于其他任何事情。

维基百科详细解释了它确实安全的原因。所以回答你的问题:是的,它足够安全。


10

UUID方案通常不仅使用伪随机元素,还使用当前系统时间和某种通常唯一的硬件ID(如果可用),例如网络MAC地址。

使用UUID的整个意义在于你信任它能够比你自己提供更好的唯一标识符。这与使用第三方加密库而不是自己编写加密算法的原理相同。自己编写可能更有趣,但通常不负责任。


那么,既然登录信息已经在那里了,为什么要使用UUID呢? - user749127

8
这里有一个测试片段供您测试其独特性。受@scalabl3评论的启发。
“有趣的是,您可以生成两个完全相同的ID,当然需要惊人的巧合、运气和神圣干预,尽管概率难以想象,但仍然可能发生!:D 是的,这不会发生。只是为了让你想象一下创建重复时的那个时刻而已!截屏视频!-scalabl3 Oct 20'15 at 19:11”
如果您感到幸运,请选中复选框,它仅检查当前生成的ID。如果您希望进行历史记录检查,请取消选中。请注意,如果您不取消选中,可能会在某些时候耗尽内存。我试图使它对CPU友好,以便在需要时可以快速中止,只需再次点击运行片段按钮或离开页面即可。

Math.log2 = Math.log2 || function(n){ return Math.log(n) / Math.log(2); }
  Math.trueRandom = (function() {
  var crypt = window.crypto || window.msCrypto;

  if (crypt && crypt.getRandomValues) {
      // if we have a crypto library, use it
      var random = function(min, max) {
          var rval = 0;
          var range = max - min;
          if (range < 2) {
              return min;
          }

          var bits_needed = Math.ceil(Math.log2(range));
          if (bits_needed > 53) {
            throw new Exception("We cannot generate numbers larger than 53 bits.");
          }
          var bytes_needed = Math.ceil(bits_needed / 8);
          var mask = Math.pow(2, bits_needed) - 1;
          // 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111

          // Create byte array and fill with N random numbers
          var byteArray = new Uint8Array(bytes_needed);
          crypt.getRandomValues(byteArray);

          var p = (bytes_needed - 1) * 8;
          for(var i = 0; i < bytes_needed; i++ ) {
              rval += byteArray[i] * Math.pow(2, p);
              p -= 8;
          }

          // Use & to apply the mask and reduce the number of recursive lookups
          rval = rval & mask;

          if (rval >= range) {
              // Integer out of acceptable range
              return random(min, max);
          }
          // Return an integer that falls within the range
          return min + rval;
      }
      return function() {
          var r = random(0, 1000000000) / 1000000000;
          return r;
      };
  } else {
      // From http://baagoe.com/en/RandomMusings/javascript/
      // Johannes Baagøe <baagoe@baagoe.com>, 2010
      function Mash() {
          var n = 0xefc8249d;

          var mash = function(data) {
              data = data.toString();
              for (var i = 0; i < data.length; i++) {
                  n += data.charCodeAt(i);
                  var h = 0.02519603282416938 * n;
                  n = h >>> 0;
                  h -= n;
                  h *= n;
                  n = h >>> 0;
                  h -= n;
                  n += h * 0x100000000; // 2^32
              }
              return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
          };

          mash.version = 'Mash 0.9';
          return mash;
      }

      // From http://baagoe.com/en/RandomMusings/javascript/
      function Alea() {
          return (function(args) {
              // Johannes Baagøe <baagoe@baagoe.com>, 2010
              var s0 = 0;
              var s1 = 0;
              var s2 = 0;
              var c = 1;

              if (args.length == 0) {
                  args = [+new Date()];
              }
              var mash = Mash();
              s0 = mash(' ');
              s1 = mash(' ');
              s2 = mash(' ');

              for (var i = 0; i < args.length; i++) {
                  s0 -= mash(args[i]);
                  if (s0 < 0) {
                      s0 += 1;
                  }
                  s1 -= mash(args[i]);
                  if (s1 < 0) {
                      s1 += 1;
                  }
                  s2 -= mash(args[i]);
                  if (s2 < 0) {
                      s2 += 1;
                  }
              }
              mash = null;

              var random = function() {
                  var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
                  s0 = s1;
                  s1 = s2;
                  return s2 = t - (c = t | 0);
              };
              random.uint32 = function() {
                  return random() * 0x100000000; // 2^32
              };
              random.fract53 = function() {
                  return random() +
                      (random() * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53
              };
              random.version = 'Alea 0.9';
              random.args = args;
              return random;

          }(Array.prototype.slice.call(arguments)));
      };
      return Alea();
  }
}());

Math.guid = function() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c)    {
      var r = Math.trueRandom() * 16 | 0,
          v = c == 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
  });
};
function logit(item1, item2) {
    console.log("Do "+item1+" and "+item2+" equal? "+(item1 == item2 ? "OMG! take a screenshot and you'll be epic on the world of cryptography, buy a lottery ticket now!":"No they do not. shame. no fame")+ ", runs: "+window.numberofRuns);
}
numberofRuns = 0;
function test() {
   window.numberofRuns++;
   var x = Math.guid();
   var y = Math.guid();
   var test = x == y || historyTest(x,y);

   logit(x,y);
   return test;

}
historyArr = [];
historyCount = 0;
function historyTest(item1, item2) {
    if(window.luckyDog) {
       return false;
    }
    for(var i = historyCount; i > -1; i--) {
        logit(item1,window.historyArr[i]);
        if(item1 == history[i]) {
            
            return true;
        }
        logit(item2,window.historyArr[i]);
        if(item2 == history[i]) {
            
            return true;
        }

    }
    window.historyArr.push(item1);
    window.historyArr.push(item2);
    window.historyCount+=2;
    return false;
}
luckyDog = false;
document.body.onload = function() {
document.getElementById('runit').onclick  = function() {
window.luckyDog = document.getElementById('lucky').checked;
var val = document.getElementById('input').value
if(val.trim() == '0') {
    var intervaltimer = window.setInterval(function() {
         var test = window.test();
         if(test) {
            window.clearInterval(intervaltimer);
         }
    },0);
}
else {
   var num = parseInt(val);
   if(num > 0) {
        var intervaltimer = window.setInterval(function() {
         var test = window.test();
         num--;
         if(num < 0 || test) {
    
         window.clearInterval(intervaltimer);
         }
    },0);
   }
}
};
};
Please input how often the calulation should run. set to 0 for forever. Check the checkbox if you feel lucky.<BR/>
<input type="text" value="0" id="input"><input type="checkbox" id="lucky"><button id="runit">Run</button><BR/>


尝试使用RFC 4122版本1(日期时间和MAC地址)UUID。 - zaph

8

我已经做了多年。从未遇到问题。

我通常设置我的数据库只有一个包含所有密钥和修改日期等信息的表格。我从来没有遇到过重复键的问题。

唯一的缺点是,当您编写一些查询以快速查找信息时,会大量复制和粘贴密钥。您不再拥有易于记忆的短ID。


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