Create a cryptographically secure random GUID in .NET
Asked Answered
G

6

39

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?

Gunplay answered 11/5, 2016 at 18:13 Comment(1)
On Windows 10 at least, .NET NewGuid uses Ole32.CoCreateGuid wich uses Rpcrt4.UuidCreate which uses BCryptPrimitives.ProcessPrng (download.microsoft.com/download/1/c/9/…). So I think with recent versions of Windows, Guid.NewGuid does create cryptographically secure GUIDs.Thompson
K
58

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);
    }
}
Kelwin answered 11/5, 2016 at 18:25 Comment(12)
Wait, is it really this simple? I thought that there was a GUID standard - don't the first few bytes define the version used, while the last few bytes define something else? I thought there was more to a GUID than random bytes, hence the question.Gunplay
There is a difference between a cryptographically secure GUID and an standarized Guid, if you want the Guid to avoid collision then use the Guid.NewGuid, else use this. Any standarized Guid will not be cryptographically secure as there are rules to generate them as you correctly said, then it's strength would be very low (if you know how 8 of the 16 bytes are generated, you only need to bruteforce these 8 bytes...)Kelwin
I hadn't thought of that. I'm using this for activation emails - generate a GUID for an account activation URL. These would be stored in the DB, so ideally they should not collide and ideally they should be cryptographically secure (very difficult to guess). I was wondering if there was a good way to achieve both. (For practical purposes I can probably use a GUID or Cryptography.getHash, but if there is a way to do things correctly, that is the option I will take.)Gunplay
@jedd.ahyoung In the past the first portion of the GUID was generated to be unique to the system as an attempt to ensure uniqueness across systems. Later it was determined that this only hurt uniqueness, and so modern GUIDs are pure randomness. If you don't mind the performance hit with Gusman's answer (shouldn't be too much), it's GUID best-practice sound.Diorio
@NexTerren Right. v1 GUIDs used the MAC address of the system. v4 GUIDs use the timestamp, I believe. I've forgotten the standards for v2, v3, v5, and v6 (I looked them up a while back for a project I was doing) but they all have specific bits set to identify the version, even the random ones - it's part of each standard.Gunplay
Well, rules of collision are to avoid collision between machines if I recall it right, it think there are a lot less of chances to have a collision between these guids on the same machine than standarized guids, the chance to get a duplicate is one between 2^128, so it's more probable that our sun explodes on our faces than get a collision XDKelwin
Fair enough. I'll use your solution! Thanks!Gunplay
@Kelwin For what it's worth, RNGCryptoServiceProvider implements IDisposable, so it should be wrapped in a using statement or a try/finally. Future readers who stumble upon this question may find that useful.Gunplay
@jedd.ahyoung I reverted your changes not because the using, that's a good practice, but because the guid was inside the using and then it was unusable outside the using block, if you want to update the code with the using statement leaving the guid definition outside the using loop it would be the perfect example :D.Kelwin
@Kelwin Yup, you were right - I wrapped it without including full changes from my code. I've edited.Gunplay
@jedd.ahyoung Lol, if I could I would give you points, now it's perfect ;)Kelwin
Related article for cross platform considerations with .net core. https://mcmap.net/q/409013/-rngcryptoserviceprovider-in-net-coreGland
S
17

Read Brad M's answer below: https://mcmap.net/q/399080/-create-a-cryptographically-secure-random-guid-in-net

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);
    }
}
Surbeck answered 5/10, 2016 at 8:50 Comment(0)
W
14

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 account for both Big Endian and Little Endian architectures.

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 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://mcmap.net/q/399080/-create-a-cryptographically-secure-random-guid-in-net
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:

References

Whinny answered 21/12, 2019 at 15:34 Comment(8)
@Benrobot Guid was invalid because of version bits Endianness. Edited my answer accordingly. Checked the GUID with the online validators and it checks out. Let me know of the results on your end.Whinny
I can't believe my endianness has been brought into question :). Yes, your updated answer does work for me as well. Thank you for the update.Ryswick
Glad it worked out for you! This has been an interesting learning experience. Thanks for pointing out there's an invalid GUID case. Most users take what's there without criticizing it.Whinny
Are GUIDs generated this way guaranteed to be unique? i.e. if two separate servers generate GUIDs can they NEVER be the same, as per regular GUIDs?Mastrianni
@AX valid question, it's very rare to have GUID collision, around 1 in 10^38. Check answers here: https://mcmap.net/q/22144/-are-guid-collisions-possible GUID v1 guarantees randomness. However GUID v4 (this answer) guarantees randomness and unpredictability.Whinny
10^38, that's way higher than the number of stars in the universe.Whinny
@Whinny Thanks - but just to be clear, it is possible. Since you didn't prepend the MAC address of the network card, it is not guaranteed to be unique so we would still need to check if it is unique before using it (e.g. by doing a lookup)?Mastrianni
@AX I don't think it's necessary, but you can definitely perform a lookup. You'd need to use a distributed database for this (think AWS Elastic Cache). However that is an in-memory database. For a persisted one you would need a central SQL/No-SQL Database with proper indexing. You can have it replicated across multiple zones for fault-tolerance and shorter round-trip delay. You should use the DataBase on ID creation only to check the ID you are creating is not an existing one. So it's not a costly operation and is used on CREATE CRUD operation only.Whinny
S
12

https://www.rfc-editor.org/rfc/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);
    }
}
Shawna answered 21/5, 2018 at 20:35 Comment(6)
And why is this important?Selimah
If an auditor or other IT security expert looks at GUIDs generated and used for secure I'd numbers, he/she will want to know if all those numbers were the kind that is hard to guess. If you randomize the entire GUID (instead of setting the version) some of the GUIDs created will appear to be type 1 or some other insecure type even if they are not. So, setting the version to the most appropriate value will help avoid some confusion. I doubt that any code will stop working if you ignore the standard. But, it's best to comply, for clearity.Shawna
@Shawna your answer is superb, put me in the right track. However the bitwise operations you are using are a bit off. Here's my updated answer.Whinny
@Whinny I compared your bitwise operations to rlamoni's and websites like freecodeformat.com/validate-uuid-guid.php and beautifyconverter.com/uuid-validator.php agree with rlamoniRyswick
@Ryswick Neither rlamoni nor I were correct. It's because of Endianness. I updated my answer below.Whinny
@Shawna accounted for version bits in LittleEndian architecture/mode (7th byte is version byte), my initial answer accounted for version bits in BigEndian architecture/mode (6th byte is version byte). My updated answer accounts for both.Whinny
M
12

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 |           - |           - |           - |                   - |
Mahmud answered 10/1, 2019 at 15:54 Comment(0)
P
8

The answer from om-ha is terrific. But I wanted to optimize it for .NET 5.0, which doesn't require the RNG crypto provider. My modified version for .NET 5.0 is below:

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://mcmap.net/q/399080/-create-a-cryptographically-secure-random-guid-in-net
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);
}

Edit here uses RandomNumberGenerator (Docs), Fill method does the following:

Fills a span with cryptographically strong random bytes

Pemmican answered 30/12, 2020 at 23:32 Comment(1)
Thanks for the improvement to my answer!Whinny

© 2022 - 2024 — McMap. All rights reserved.