577

使用 UUID 来唯一标识某物有多安全(我将它用于上传到服务器的文件)?据我了解,它基于随机数。然而,在我看来,如果有足够的时间,它最终会自我重复,只是纯属偶然。是否有更好的系统或某种类型的模式来缓解这个问题?

4

12 回答 12

578

非常安全:

一个人被陨石击中的年风险估计为 170 亿分之一,这意味着概率约为 0.00000000006 (6 × 10 -11 ),相当于创建几十万亿个 UUID的概率在一年内,并且有一个重复。换句话说,仅在接下来的 100 年每秒生成 10 亿个 UUID 之后,仅创建一个副本的概率约为 50%。

警告:

但是,这些概率仅在使用足够的熵生成 UUID 时才成立。否则,重复的概率可能会显着提高,因为统计离散度可能会更低。在分布式应用程序需要唯一标识符的情况下,即使合并来自多个设备的数据,UUID 也不会发生冲突,每个设备上使用的种子和生成器的随机性在应用程序的生命周期内必须是可靠的。如果这不可行,RFC4122 建议使用命名空间变体。

资料来源:维基百科文章中关于通用唯一标识符的随机 UUID 重复概率部分(链接指向从 2016 年 12 月开始的修订,然后编辑重新修改了该部分)。

另请参阅同一篇通用唯一标识符文章Collisions中有关同一主题的当前部分。

于 2009-07-20T18:09:50.200 回答
181

如果“给定足够的时间”是指 100 年,并且你以每秒 10 亿的速度创造它们,那么是的,100 年后你有 50% 的机会发生碰撞。

于 2009-07-20T18:14:13.780 回答
132

UUID 的类型不止一种,因此“安全程度”取决于您使用的类型(UUID 规范称为“版本”)。

  • 版本 1 是基于时间的加上 MAC 地址的 UUID。128 位包含 48 位网卡 MAC 地址(由制造商唯一分配)和 60 位时钟,分辨率为 100 纳秒。该时钟在公元 3603 年结束,因此这些 UUID 至少在此之前是安全的(除非您每秒需要超过 1000 万个新 UUID,或者有人克隆了您的网卡)。我说“至少”是因为时钟从 1582 年 10 月 15 日开始,所以在时钟结束后大约有 400 年的时间,甚至还有很小的重复可能性。

  • 版本 4 是随机数 UUID。有六个固定位,其余的 UUID 是 122 位随机性。请参阅Wikipedia或其他分析,这些分析描述了重复的可能性很小。

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

  • 版本 2 与版本 1 类似,但时钟更小,因此它会更快地环绕。但由于第 2 版 UUID 用于 DCE,因此您不应该使用它们。

因此,对于所有实际问题,它们都是安全的。如果您对将其留给概率感到不舒服(例如,您是那种担心地球会在一生中被大型小行星摧毁的人),只需确保使用版本 1 UUID 并保证它是唯一的(在你有生之年,除非你计划活过公元 3603 年)。

那么为什么不是每个人都简单地使用版本 1 UUID?这是因为版本 1 UUID 揭示了生成它的机器的 MAC 地址,并且它们是可预测的——这两件事可能对使用这些 UUID 的应用程序有安全影响。

于 2011-08-06T00:32:28.703 回答
23

这个问题的答案可能很大程度上取决于 UUID 版本。

许多 UUID 生成器使用版本 4 随机数。但是,其中许多使用伪随机数生成器来生成它们。

如果使用带有小周期的不良种子 PRNG 来生成 UUID,我会说它根本不是很安全。一些随机数生成器的方差也很差。即比其他人更频繁地偏爱某些数字。这不会很好地工作。

因此,它仅与用于生成它的算法一样安全。

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

在我的例子中,我使用的 PRNG 是一个 mersenne twister,我很小心它的播种方式,它来自多个来源,包括 /dev/urandom。Mersenne twister 的周期为 2^19937 − 1。在我看到重复的 uuid 之前需要很长时间。

所以选择一个好的库或者自己生成它,并确保你使用了一个像样的 PRNG 算法。

于 2012-01-04T02:51:49.833 回答
14

我同意其他答案。UUID 对于几乎所有实际用途来说都足够安全1,当然对于您的用途也是如此。

但是假设(假设地)他们不是。

是否有更好的系统或某种类型的模式来缓解这个问题?

这里有几种方法:

  1. 使用更大的 UUID。例如,不要使用 128 个随机位,而是使用 256 或 512 或......您添加到类型 4 样式 UUID 的每个位都会将碰撞概率降低一半,假设您有可靠的熵源2 .

  2. 构建一个集中式或分布式服务,生成 UUID 并记录它曾经发布的每一个。每次生成一个新的 UUID 时,它都会检查该 UUID 是否以前从未发布过。如果我们假设运行该服务的人绝对值得信赖、廉洁奉公等等,那么这样的服务在技术上就可以直接实施(我认为)。不幸的是,它们不是……尤其是当政府的安全组织有可能进行干预时。因此,这种方法可能是不切实际的,并且在现实世界中可能是3不可能的。


1 - 如果UUID的唯一性决定了核导弹是否在您的国家首都发射,您的许多同胞不会相信“概率极低”。因此我的“几乎所有”资格。
2 - 这是一个哲学问题。有什么东西真的是随机的吗?如果不是,我们怎么知道?我们所知道的宇宙是模拟的吗?有没有可以想象的“调整”物理定律来改变结果的上帝?
3 - 如果有人知道有关此问题的任何研究论文,请发表评论。

于 2018-09-06T13:00:59.747 回答
13

引用维基百科

因此,任何人都可以创建一个 UUID 并使用它来识别某些东西,并且有合理的信心,该标识符永远不会被任何人无意中用于其他任何事情

它继续非常详细地解释了它实际上是多么安全。所以回答你的问题:是的,它足够安全。

于 2009-07-20T18:10:51.447 回答
11

对于 UUID4,我认为在一个边长为 360,000 公里的立方体形盒子中,ID 的数量大约与沙粒的数量一样多。这是一个边长约 2 1/2 倍于木星直径的盒子。

工作以便有人可以告诉我我是否搞砸了单位:

  • 沙粒体积 0.00947mm^3 ( Guardian )
  • UUID4 有 122 个随机位 -> 5.3e36 个可能值(维基百科
  • 那么多沙粒的体积 = 5.0191e34 mm^3 或 5.0191e+25m^3
  • 具有该体积的立方体的边长 = 3.69E8m 或 369,000km
  • 木星直径:139,820 公里(谷歌)
于 2019-11-01T12:57:48.137 回答
9

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

使用 UUID 的全部意义在于,您相信它在提供唯一 ID 方面比您自己能够做得更好。这与使用 3rd 方密码库而不是滚动自己的密码库背后的理由相同。自己做可能会更有趣,但这样做通常不太负责任。

于 2009-07-20T18:53:46.463 回答
8

多年来一直这样做。永远不要遇到问题。

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

它的唯一缺点是,当您编写一些查询以快速查找某些信息时,您需要对密钥进行大量复制和粘贴。您不再拥有简短易记的 ID。

于 2009-07-20T18:58:05.190 回答
8

这是一个测试片段,供您测试它的独特性。受@scalabl3 评论的启发

有趣的是,你可以连续生成 2 个相同的,当然,在巧合、运气和神圣干预的令人难以置信的水平上,尽管几率高不可测,它仍然是可能的!:D 是的,它不会发生。只是为了想一想您创建副本的那一刻而说的有趣!截图视频!– scalabl3 2015 年 10 月 20 日 19:11

如果您觉得幸运,请选中复选框,它只检查当前生成的 id。如果您希望进行历史检查,请不要选中它。请注意,如果您不选中它,您可能会在某个时候用完 ram。我试图让它对 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/>

于 2016-08-19T11:50:04.147 回答
3

我不知道这对您是否重要,但请记住GUID 是全局唯一的,但 GUID 的子字符串不是.

于 2009-07-20T22:18:54.177 回答
0

我应该提到我在亚马逊上购买了两个外部希捷驱动器,它们具有相同的设备 UUID,但不同的 PARTUUID。据推测,他们用来格式化驱动器的克隆软件也只是复制了 UUID。

显然,由于克隆或复制过程有缺陷而不是随机巧合,UUID 冲突更有可能发生。在计算 UUID 风险时请记住这一点。

于 2021-04-27T10:18:28.067 回答