【问题标题】:Create a cryptographically secure random GUID in .NET在 .NET 中创建加密安全的随机 GUID
【发布时间】:2016-09-07 07:29:47
【问题描述】:

我想在 .NET 中创建一个加密安全的 GUID (v4)。

.NET 的 Guid.NewGuid() 函数在加密上并不安全,但 .NET 确实提供了 System.Security.Cryptography.RNGCryptoServiceProvider 类。

我希望能够将随机数函数作为委托传递给Guid.NewGuid(或者甚至传递一些提供生成器接口的类),但默认实现似乎无法实现。

我可以同时使用System.GUIDSystem.Security.Cryptography.RNGCryptoServiceProvider 创建一个加密安全的GUID 吗?

【问题讨论】:

  • 路过近距离投票有什么理由吗?评论和反馈总是很受欢迎,因为它们很少见。
  • 至少在 Windows 10 上,.NET NewGuid 使用 Ole32.CoCreateGuid,它使用 Rpcrt4.UuidCreate,它使用 BCryptPrimitives.ProcessPrng (download.microsoft.com/download/1/c/9/…)。所以我认为对于最新版本的 Windows,Guid.NewGuid 确实创建了加密安全的 GUID。

标签: c# .net security guid


【解决方案1】:

可以,Guid 允许您使用字节数组创建 Guid,RNGCryptoServiceProvider 可以生成随机字节数组,因此您可以使用输出来提供新的 Guid:

public Guid CreateCryptographicallySecureGuid() 
{
    using (var provider = new RNGCryptoServiceProvider()) 
    {
        var bytes = new byte[16];
        provider.GetBytes(bytes);

        return new Guid(bytes);
    }
}

【讨论】:

  • 等等,真的这么简单吗?我认为有一个 GUID 标准 - 前几个字节不是定义使用的版本,而最后几个字节定义其他东西吗?我认为 GUID 不仅仅是随机字节,因此问题。
  • 加密安全 GUID 和标准化 Guid 之间存在差异,如果您希望 Guid 避免冲突,则使用 Guid.NewGuid,否则使用它。任何标准化的 Guid 都不会是加密安全的,因为有规则可以按照您正确的说法生成它们,那么它的强度会非常低(如果您知道 16 个字节中的 8 个是如何生成的,那么您只需要暴力破解这 8 个字节.. .)
  • 好吧,如果我没记错的话,碰撞规则是为了避免机器之间的碰撞,它认为在同一台机器上这些 guid 之间发生碰撞的机会比标准化的 guid 少得多,获得副本的机会介于 2^128 之间,因此我们的太阳更有可能在我们的脸上爆炸而不是发生碰撞 XD
  • @Gusman 对于它的价值,RNGCryptoServiceProvider 实现了IDisposable,所以它应该包含在using 语句或try/finally 中。偶然发现这个问题的未来读者可能会发现这很有用。
  • 有关 .net 核心的跨平台注意事项的相关文章。 stackoverflow.com/a/38644970/494635
【解决方案2】:

阅读下面 Brad M 的回答:https://stackoverflow.com/a/54132397/113535

如果有人对这里感兴趣的是上面针对 .NET Core 1.0 (DNX) 调整的示例代码

public Guid CreateCryptographicallySecureGuid()
{
    using (var provider = System.Security.Cryptography.RandomNumberGenerator.Create())
    {
        var bytes = new byte[16];
        provider.GetBytes(bytes);

        return new Guid(bytes);
    }
}

【讨论】:

    【解决方案3】:

    https://www.rfc-editor.org/rfc/rfc4122 表示应该修复一些位,以表明此 GUID 是版本 4(随机)的。这是更改设置/取消设置这些位的代码。

    public Guid CreateCryptographicallySecureGuid()
    {
        using (var provider = new RNGCryptoServiceProvider())
        {
            var bytes = new byte[16];
            provider.GetBytes(bytes);
            bytes[8] = (byte)(bytes[8] & 0xBF | 0x80);
            bytes[7] = (byte)(bytes[7] & 0x4F | 0x40);
            return new Guid(bytes);
        }
    }
    

    【讨论】:

    • 为什么这很重要?
    • 如果审计员或其他 IT 安全专家查看生成并用于安全 Id 数字的 GUID,他/她会想知道所有这些数字是否都是难以猜测的数字。如果您随机化整个 GUID(而不是设置版本),一些创建的 GUID 将显示为类型 1 或其他不安全类型,即使它们不是。因此,将版本设置为最合适的值将有助于避免一些混淆。我怀疑如果您忽略标准,任何代码都会停止工作。但是,为了清楚起见,最好遵守。
    • @rlamoni 你的回答太棒了,让我走上正轨。但是,您使用的按位运算有点偏离。这是我更新的answer
    • @om-ha 我将您的按位运算与 rlamoni 和 freecodeformat.com/validate-uuid-guid.phpbeautifyconverter.com/uuid-validator.php 等网站进行了比较,同意 rlamoni
    • @Benrobot rlamoni 和我都不正确。这是因为字节序。我更新了我的答案below
    【解决方案4】:

    如果您至少使用 c# 7.2 和 netcoreapp2.1(或 System.Memory),这是最快/最有效的方法。

    public static Guid CreateCryptographicallySecureGuid()
    {
        Span<byte> bytes = stackalloc byte[16];
        RandomNumberGenerator.Fill(bytes);
        return new Guid(bytes);
    }
    

    我创建了一个基准,将其与接受的答案进行比较。我修改它以使用RandomNumberGenerator 的静态实现,因为GetBytes() 是线程安全的。 (虽然我看到的唯一保证是RNGCryptoServiceProvider 有一个线程安全的实现......其他实现可能没有)

    [MemoryDiagnoser]
    public class Test
    {
        private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
    
        [Benchmark]
        public void Heap()
        {
            var bytes = new byte[16];
            _rng.GetBytes(bytes);
            new Guid(bytes);
        }
    
        [Benchmark]
        public void Fill()
        {
            Span<byte> bytes = stackalloc byte[16];
            RandomNumberGenerator.Fill(bytes);
            new Guid(bytes);
        }
    }
    
    | Method |     Mean |     Error |    StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
    |------- |---------:|----------:|----------:|------------:|------------:|------------:|--------------------:|
    |   Heap | 129.4 ns | 0.3074 ns | 0.2725 ns |      0.0093 |           - |           - |                40 B |
    |   Fill | 116.5 ns | 0.3440 ns | 0.2872 ns |           - |           - |           - |                   - |
    

    【讨论】:

      【解决方案5】:

      2020修改版

      我发现@rlamoni 的answer 很棒。只需对其按位运算稍作修改,即可正确反映 GUID 版本 4 识别位,以适应 Big Endian 和 Little Endian 架构。

      更具体地说明我的回答中的更正:

      1. 修改后的字节应该是第 7 和第 9。
      2. 已更正按位与操作数。

      更新

      正如用户Benrobot 指出的那样,Guid 无效。我想是因为他的设备Endianness 与我的不同。

      这促使我进一步完善我的答案。除了我在原始答案中指出的两个更正之外,这里还有一些我的答案的改进:

      1. 占当前计算机架构的Endianness
      2. 添加了不言自明的常量和变量标识符,而不是常量文字。
      3. 使用 simple using statement 不需要大括号。
      4. 添加了一些 cmets 并需要 using directive
      using System;
      using System.Security.Cryptography;
      
      /// Generates a cryptographically secure random Guid.
      ///
      /// Characteristics
      ///     - Variant: RFC 4122
      ///     - Version: 4
      /// RFC
      ///     https://www.rfc-editor.org/rfc/rfc4122#section-4.1.3
      /// Stackoverflow
      ///     https://stackoverflow.com/a/59437504/10830091
      static Guid CreateCryptographicallySecureRandomRFC4122Guid()
      {
          using var cryptoProvider = new RNGCryptoServiceProvider();
      
          // byte indices
          int versionByteIndex = BitConverter.IsLittleEndian ? 7 : 6;
          const int variantByteIndex = 8;
      
          // version mask & shift for `Version 4`
          const int versionMask = 0x0F;
          const int versionShift = 0x40;
      
          // variant mask & shift for `RFC 4122`
          const int variantMask = 0x3F;
          const int variantShift = 0x80;
                  
          // get bytes of cryptographically-strong random values
          var bytes = new byte[16];
          cryptoProvider.GetBytes(bytes);
      
          // Set version bits -- 6th or 7th byte according to Endianness, big or little Endian respectively
          bytes[versionByteIndex] = (byte)(bytes[versionByteIndex] & versionMask | versionShift);
      
          // Set variant bits -- 9th byte
          bytes[variantByteIndex] = (byte)(bytes[variantByteIndex] & variantMask | variantShift);
      
          // Initialize Guid from the modified random bytes
          return new Guid(bytes);
      }
      

      在线验证器

      检查生成的 GUID 的有效性:

      参考文献

      • RFC4122 版本的Section
      • GuidOne,一个被低估的 GUID 库。
      • This GUID 指南。
      • Cryptosys Uuid.cs C# class.

      【讨论】:

      • @Benrobot Guid 由于版本位字节序而无效。相应地编辑了我的答案。用在线验证器检查了 GUID,它检查了。让我知道你的结果。
      • 我不敢相信我的字节序已经受到质疑:)。是的,您更新的答案也对我有用。谢谢你的更新。
      • 很高兴它为你解决了!这是一次有趣的学习经历。感谢您指出存在无效的 GUID 案例。大多数用户接受现有的东西而不批评它。
      【解决方案6】:

      answer from om-ha 很棒。但我想为 .NET 5.0 优化它,它不需要 RNG 加密提供程序。我对 .NET 5.0 的修改版本如下:

      using System;
      using System.Security.Cryptography;
      
      /// Generates a cryptographically secure random Guid.
      ///
      /// Characteristics
      ///     - GUID Variant: RFC 4122
      ///     - GUID Version: 4
      ///     - .NET 5
      /// RFC
      ///     https://tools.ietf.org/html/rfc4122#section-4.1.3
      /// Stackoverflow
      ///     https://stackoverflow.com/a/59437504/10830091
      static Guid CreateCryptographicallySecureRandomRFC4122Guid()
      {
          // Byte indices
          int versionByteIndex = BitConverter.IsLittleEndian ? 7 : 6;
          const int variantByteIndex = 8;
      
          // Version mask & shift for `Version 4`
          const int versionMask = 0x0F;
          const int versionShift = 0x40;
      
          // Variant mask & shift for `RFC 4122`
          const int variantMask = 0x3F;
          const int variantShift = 0x80;
      
          // Get bytes of cryptographically-strong random values
          var bytes = new byte[16];
      
          RandomNumberGenerator.Fill(bytes);
      
          // Set version bits -- 6th or 7th byte according to Endianness, big or little Endian respectively
          bytes[versionByteIndex] = (byte)(bytes[versionByteIndex] & versionMask | versionShift);
      
          // Set variant bits -- 9th byte
          bytes[variantByteIndex] = (byte)(bytes[variantByteIndex] & variantMask | variantShift);
      
          // Initialize Guid from the modified random bytes
          return new Guid(bytes);
      }
      

      此处编辑使用RandomNumberGenerator (Docs),Fill 方法执行以下操作:

      用加密的强随机字节填充范围

      【讨论】:

      • 感谢您对我的回答的改进!
      猜你喜欢
      • 2014-01-23
      • 2013-08-16
      • 1970-01-01
      • 2014-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-18
      • 1970-01-01
      相关资源
      最近更新 更多