我想在.NET中创建一个加密安全的GUID(v4)。
.NET的Guid.NewGuid()
函数在加密方面不安全,但.NET确实提供了System.Security.Cryptography.RNGCryptoServiceProvider
类。
我希望能够将一个随机数函数作为委托传递给Guid.NewGuid
(或者甚至传递一些提供生成器接口的类),但它看起来并不像是可能的默认实现。
我可以一起使用System.GUID
和System.Security.Cryptography.RNGCryptoServiceProvider
来创建加密安全的GUID吗?
答案 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);
}