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

Create a cryptographically secure random GUID in .NET

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

.NET 的 Guid.NewGuid() 函数不是加密安全的,但 .NET 确实提供了 System.Security.Cryptography.RNGCryptoServiceProvider class.

我希望能够将随机数函数作为委托传递给 Guid.NewGuid(或者甚至传递一些提供生成器接口的 class),但它看起来不像这可以通过默认实现实现。

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

是的,Guid allows you to create a Guid using a byte array, and RNGCryptoServiceProvider 可以生成随机字节数组,因此您可以使用输出来提供新的 Guid:

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

        return new Guid(bytes);
    }
}

阅读下面 Brad M 的回答:

如果有人感兴趣,这里是针对 .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);
    }
}

https://www.rfc-editor.org/rfc/rfc4122 说有一些位应该被修复以表明这个 GUID 是第 4 版(随机)。这是更改为 set/unset 这些位的代码。

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

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

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 |           - |           - |           - |                   - |

2020年修改版

我发现@rlamoni 的 很棒。只需要对其按位运算进行一点修改即可正确反映 GUID 版本 4 标识位,以说明大端和小端架构。

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

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

更新

作为用户 Benrobot pointed out, Guid was invalid. I suppose because of his device Endianness 与我不同。

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

  1. 占当前计算机体系结构的Endianness
  2. 添加了不言自明的常量和变量标识符,而不是常量文字。
  3. 使用了不需要大括号的 simple 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
/// Whosebug
///     
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.

太棒了。但我想针对 .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
/// Whosebug
///     
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 方法执行以下操作:

Fills a span with cryptographically strong random bytes