Create a cryptographically secure random GUID in .

2020-02-20 07:23发布

问题:

I want to create a cryptographically secure GUID (v4) in .NET.

.NET's Guid.NewGuid() function is not cryptographically secure, but .NET does provide the System.Security.Cryptography.RNGCryptoServiceProvider class.

I would like to be able to pass a random number function as a delegate to Guid.NewGuid (or even pass some class that provides a generator interface) but it doesn't look as though that is possible with the default implementation.

Can I create a cryptographically secure GUID by using System.GUID and System.Security.Cryptography.RNGCryptoServiceProvider together?

回答1:

Yes you can, Guid allows you to create a Guid using a byte array, and RNGCryptoServiceProvider can generate a random byte array, so you can use the output to feed a new Guid:

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

        return new Guid(bytes);
    }
}


回答2:

Read Brad M's answer below: https://stackoverflow.com/a/54132397/113535

If anyone is interested here is the above sample code adjusted for .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://tools.ietf.org/html/rfc4122 says there are a few bits that should be fixed in order to indicate that this GUID is a version-4 (random) one. Here is the code altered to set/unset these bits.

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


回答4:

If you are using at least c# 7.2 and netcoreapp2.1 (or System.Memory), this is the fastest/most efficient approach.

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

I created a benchmark comparing this to the accepted answer. I modified it to use a static implementation of RandomNumberGenerator since GetBytes() is thread safe. (although the only guarantee I see is that RNGCryptoServiceProvider has a thread safe implementation...it's possible other implementations do not)

[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 Modified Version

I found @rlamoni's answer to be great. Just needs a little bit modifications in its bitwise operations to correctly reflect GUID version 4 identifying bits.

To be more specific about the corrections in my answer:

  1. The modified bytes should be 7th and 9th.
  2. The bitwise-and operands have been corrected.

Update

As the user Benrobot pointed out, Guid was invalid. I suppose because of his device Endianness being different to mine.

This prompted me to further enhance my answer. In addition to the two corrections I pointed out above in my original answer, here are few more enhancements of my answer:

  1. Accounted for Endianness of the current computer architecture.
  2. Added self-explanatory constant & variable identifiers instead of constant literals.
  3. Used simple using statement that doesn't require braces.
  4. Added some comments and required using directive.
using System;
using System.Security.Cryptography;

/// Generates random Guid.
///
/// Characteristics
///     1. Variant: RFC 4122
///     2. Version: 4
/// RFC
///     https://tools.ietf.org/html/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);
}

Online validators

To check for the validity of the generated GUID:

  • http://guid.one/parse
  • https://www.beautifyconverter.com/uuid-validator.php
  • https://www.freecodeformat.com/validate-uuid-guid.php
  • http://guid.us/Test/GUID

References

  • RFC4122 Version's Section.
  • GuidOne, an underrated GUID library.
  • This GUID guide.
  • Cryptosys Uuid.cs C# class.