在 .NET 中创建加密安全的随机 GUID

Create a cryptographically secure random GUID in .NET

提问人: 提问时间:5/12/2016 更新时间:12/20/2022 访问量:14170

问:

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

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

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

是否可以同时使用和创建加密安全的 GUID?System.GUIDSystem.Security.Cryptography.RNGCryptoServiceProvider

C# .NET 安全 GUID

评论

3赞 Simon Mourier 1/18/2020
至少在 Windows 10 上,.NET NewGuid 使用 Ole32.CoCreateGuid 而使用 Rpcrt4.UuidCreate,后者使用 BCryptPrimitives.ProcessPrng (download.microsoft.com/download/1/c/9/...)。因此,我认为在最新版本的 Windows 中,Guid.NewGuid 确实创建了加密安全的 GUID。

答:

58赞 Gusman 5/12/2016 #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);
    }
}

评论

2赞 5/12/2016
等等,真的这么简单吗?我以为有一个 GUID 标准 - 前几个字节不是定义使用的版本,而最后几个字节定义其他内容吗?我认为 GUID 比随机字节更多,因此出现了问题。
2赞 Gusman 5/12/2016
加密安全 GUID 和标准化 Guid 之间存在差异,如果希望 Guid 避免冲突,请使用 Guid.NewGuid,否则使用此。任何标准化的 Guid 都不会在加密上是安全的,因为正如您正确所说,有规则可以生成它们,那么它的强度将非常低(如果您知道 16 个字节中的 8 个是如何生成的,您只需要暴力破解这 8 个字节......
1赞 Gusman 5/12/2016
好吧,如果我没记错的话,碰撞规则是为了避免机器之间的碰撞,它认为在同一台机器上这些 guid 之间发生冲突的机会比标准化的 guid 要少得多,获得副本的机会是 2^128 之间,所以我们的太阳在我们的脸上爆炸的可能性比碰撞 XD 的可能性更大
3赞 5/12/2016
@Gusman 就其价值而言,implements ,因此它应该包装在语句或 .未来偶然发现这个问题的读者可能会发现这很有用。RNGCryptoServiceProviderIDisposableusingtry/finally
1赞 Damien Sawyer 4/5/2018
有关 .net core 的跨平台注意事项的相关文章。stackoverflow.com/a/38644970/494635
17赞 Kane 10/5/2016 #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);
    }
}
12赞 rlamoni 5/22/2018 #3

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

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);
    }
}

评论

0赞 Tiago Freitas Leal 6/11/2018
为什么这很重要?
7赞 rlamoni 6/13/2018
如果审计员或其他 IT 安全专家查看生成并用于安全 I 数字的 GUID,他/她会想知道所有这些数字是否都是难以猜测的数字。如果随机化整个 GUID(而不是设置版本),则创建的某些 GUID 将显示为类型 1 或其他不安全的类型,即使它们不是。因此,将版本设置为最合适的值将有助于避免一些混淆。我怀疑如果你忽略这个标准,任何代码都会停止工作。但是,为了清楚起见,最好遵守。
1赞 om-ha 12/26/2019
@rlamoni你的答案很棒,请把我放在正确的轨道上。但是,您正在使用的按位运算有点偏离。这是我更新的答案
0赞 Benrobot 2/15/2020
@om哈,我将你的按位操作与 rlamoni 和 freecodeformat.com/validate-uuid-guid.php 等网站进行了比较,beautifyconverter.com/uuid-validator.php 同意 rlamoni
0赞 om-ha 2/17/2020
@Benrobot 拉莫尼和我都说不对。这是因为字节序。我在下面更新了我的答案。
12赞 Brad M 1/10/2019 #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);
}

我创建了一个基准,将其与公认的答案进行比较。我修改了它以使用自线程安全的静态实现。(尽管我看到的唯一保证是它有一个线程安全的实现......其他实现可能没有)RandomNumberGeneratorGetBytes()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 |           - |           - |           - |                   - |
11赞 om-ha 12/21/2019 #5

2020 修改版

我发现@rlamoni的答案很棒。只需对其按位操作进行少量修改,即可正确反映 GUID 版本 4 标识位,以同时考虑 Big Endian 和 Little Endian 体系结构。

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

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

更新

正如用户 Benrobot 所指出的,Guid 是无效的。我想是因为他的设备字节序与我的不同。

这促使我进一步改进我的答案。除了我上面在原始答案中指出的两个更正之外,以下是我的答案的更多改进:

  1. 考虑了当前计算机体系结构的字节序。
  2. 添加了不言自明的常量和变量标识符,而不是常量文字。
  3. 使用简单,不需要牙套。using statement
  4. 添加了一些注释和必需的 .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版本的部分
  • GuidOne,一个被低估的 GUID 库。
  • GUID 指南。
  • Cryptosys C# Uuid.cs

评论

0赞 om-ha 2/17/2020
@Benrobot Guid 由于版本位字节序而无效。相应地编辑了我的答案。已使用在线验证程序检查 GUID,并签出。让我知道你这边的结果。
1赞 Benrobot 2/18/2020
我简直不敢相信我的字节序:)受到了质疑。是的,您更新的答案也对我有用。感谢您的更新。
0赞 om-ha 2/18/2020
很高兴它对你有用!这是一次有趣的学习经历。感谢您指出存在无效的 GUID 情况。大多数用户在不批评的情况下接受那里的东西。
0赞 A X 11/25/2022
以这种方式生成的 GUID 是否保证是唯一的?即,如果两个单独的服务器生成 GUID,它们是否可以按照常规 GUID 永远相同?
0赞 om-ha 11/25/2022
@AX有效的问题,GUID 冲突非常罕见,大约 1/10^38。在此处查看答案:stackoverflow.com/questions/184869/are-guid-collisions-possible GUID v1 保证随机性。但是,GUID v4(此答案)保证了随机性和不可预测性。
6赞 Michael Argentini 12/31/2020 #6

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 -- 8th byte
    bytes[variantByteIndex] = (byte)(bytes[variantByteIndex] & variantMask | variantShift);

    // Initialize Guid from the modified random bytes
    return new Guid(bytes);
}

“编辑此处”使用 RandomNumberGeneratorDocs),方法执行以下操作:Fill

用加密性强的随机字节填充跨度

评论

0赞 om-ha 8/14/2021
感谢您对我的回答的改进!