First of all a quick background. Microsoft's Guid is their implementation of the Universally Unique IDentifier (UUID) outlined in RFC 4122. UUIDs are 128 bits, and the Guid class generates version 4 UUIDs, meaning that all bits except those defining the version and variant of the UUID are "random." Please note that 4 bits are used for the version number, and two bits are used for the variant — so it's not a 128 bit random number, it's a 122 bit random number.
I looked into how these Guids are created in the .NET framework. Turns out Guid.NewGuid() simply calls the CoCreateGuid function on the native ole32.dll, which in turn calls the RPC function UuidCreate. And from its remarks:
The UuidCreate function generates a UUID that cannot be traced to the ethernet address of the computer on which it was generated. It also cannot be associated with other UUIDs created on the same computer.Some care has been taken when generating these Guids, but the documentation is far from fullfilling. It's still unclear how easy they are to predict. So, assuming that we cannot trust Guids to be all that "secure", what to do? I've looked around for code that generates a Guid based on the output of a cryptographically strong RNG but couldn't find a good example — so I wrote my own generator that uses the RngCryptoServiceProvider. That way, we know where the bits are coming from. Since it generates proper Guid instances it should be fairly easy to plug it into existing code, e.g. replacing Guid.NewGuid() with SecureGuid.NewGuid(). Also remember to look out for Guids created by constructor: new Guid().
The code
Here's what the code could look like if you wanted to generate a GUID using random bytes from the frameworks's cryptograpically strong RNG. Note the first four bits of the time_hi_and_ver variable is set to version number four, and the first two bits of byte number eight is set according to the variant. Have a look at RFC 4122 for more details. Apart from that, the code should be straightforward to understand.using System;
using System.Security.Cryptography;
namespace SecureGuidDemo
{
class SecureGuid
{
public static Guid NewGuid()
{
byte[] bytes = { 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
using (var rng = new RNGCryptoServiceProvider())
{
rng.GetBytes(bytes);
}
var time = BitConverter.ToUInt32(bytes,0);
var time_mid = BitConverter.ToUInt16(bytes,4);
var time_hi_and_ver = BitConverter.ToUInt16(bytes,6);
time_hi_and_ver = (ushort)((time_hi_and_ver | 0x4000) & 0x4FFF);
bytes[8] = (byte)((bytes[8] | 0x80) & 0xBF);
return new Guid(time,time_mid,time_hi_and_ver,
bytes[8],bytes[9],bytes[10],bytes[11],bytes[12],bytes[13],
bytes[14],bytes[15]);
}
}
}
You might look at the code and find it funny that I used the constructor that takes an int, short, short, and byte's. The reason is that I found a bug when creating Guids based on byte arrays. The above code does not trigger the bug, so it should work now and should also work after the bug is fixed (if they decide to do so). I'm in the process of verifying the bug with Microsoft, I'll probably put something up on my blog about it when that's settled.
No comments:
Post a Comment