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

时间:2016-05-11 18:13:29

标签: c# .net security guid

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

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

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

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

6 个答案:

答案 0 :(得分:35)

是的,你可以,Guid允许你使用字节数组创建一个Guid,而RNGCryptoServiceProvider可以生成一个随机字节数组,这样你就可以使用输出来提供一个新的Guid:

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

        return new Guid(bytes);
    }
}

答案 1 :(得分:14)

如果有人对此感兴趣,请参阅上面针对.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);
    }
}

答案 2 :(得分:4)

https://tools.ietf.org/html/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);
    }
}

答案 3 :(得分:3)

如果您至少使用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 |           - |           - |           - |                   - |

答案 4 :(得分:0)

我发现@rlamoni的answer很棒。只需对其按位操作进行一些修改即可正确反映GUID版本4的识别位。

要更详细地说明我的答案中的更正:修改后的字节应为7和9。并且按位和运算操作数已得到纠正。

public Guid CreateCryptographicallySecureGuid()
{
    using (var provider = new RNGCryptoServiceProvider())
    {
        var bytes = new byte[16];
        provider.GetBytes(bytes);
        // 7th byte
        bytes[6] = (byte)(bytes[6] & 0x0F | 0x40);
        // 9th byte
        bytes[8] = (byte)(bytes[8] & 0x3F | 0x80);
        return new Guid(bytes);
    }
}

来源:

答案 5 :(得分:0)

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

using System;
using System.Security.Cryptography;

/// Generates a cryptographically secure random Guid.
///
/// Characteristics
///     - Variant: RFC 4122
///     - Version: 4
/// 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);
}