提问人: 提问时间:5/12/2016 更新时间:12/20/2022 访问量:14170
在 .NET 中创建加密安全的随机 GUID
Create a cryptographically secure random GUID in .NET
问:
我想在 .NET 中创建加密安全的 GUID (v4)。
.NET 的函数在加密上并不安全,但 .NET 确实提供了该类。Guid.NewGuid()
System.Security.Cryptography.RNGCryptoServiceProvider
我希望能够将随机数函数作为委托传递给(甚至传递一些提供生成器接口的类),但默认实现似乎无法做到这一点。Guid.NewGuid
是否可以同时使用和创建加密安全的 GUID?System.GUID
System.Security.Cryptography.RNGCryptoServiceProvider
答:
是的,你可以,Guid 允许你使用字节数组创建 Guid,而 RNGCryptoServiceProvider 可以生成随机字节数组,因此你可以使用输出来提供新的 Guid:
public Guid CreateCryptographicallySecureGuid()
{
using (var provider = new RNGCryptoServiceProvider())
{
var bytes = new byte[16];
provider.GetBytes(bytes);
return new Guid(bytes);
}
}
评论
RNGCryptoServiceProvider
IDisposable
using
try/finally
阅读下面 Brad M 的回答: https://stackoverflow.com/a/54132397/113535
如果有人感兴趣,这里是针对 .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(随机)的 GUID。这是为设置/取消设置这些位而更改的代码。
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
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 标识位,以同时考虑 Big Endian 和 Little Endian 体系结构。
更具体地说,我的回答中的更正:
- 修改后的字节应为第 7 和第 9 个。
- 按位和操作数已更正。
更新
正如用户 Benrobot 所指出的,Guid 是无效的。我想是因为他的设备字节序与我的不同。
这促使我进一步改进我的答案。除了我上面在原始答案中指出的两个更正之外,以下是我的答案的更多改进:
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://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);
}
在线验证者
若要检查生成的 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
引用
评论
om-ha 的答案太棒了。但我想针对 .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
/// 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 -- 8th byte
bytes[variantByteIndex] = (byte)(bytes[variantByteIndex] & variantMask | variantShift);
// Initialize Guid from the modified random bytes
return new Guid(bytes);
}
“编辑此处”使用 RandomNumberGenerator
(Docs),方法执行以下操作:Fill
用加密性强的随机字节填充跨度
评论