Question

I'm looking at using a Guid as a random anonymous visitor identifier for a website (stored both as a cookie client-size, and in a db server-side), and I wanted a cryptographically strong way of generating Guids (so as to minimize the chance of collisions).

For the record, there are 16 bytes (or 128 bits) in a Guid.

This is what I have in mind:

/// <summary>
/// Generate a cryptographically strong Guid
/// </summary>
/// <returns>a random Guid</returns>
private Guid GenerateNewGuid()
{
    byte[] guidBytes = new byte[16]; // Guids are 16 bytes long
    RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
    random.GetBytes(guidBytes);
    return new Guid(guidBytes);
}

Is there a better way to do this?

Edit: This will be used for two purposes, a unique Id for a visitor, and a transaction Id for purchases (which will briefly be the token needed for viewing/updating sensitive information).

Était-ce utile?

La solution 5

Autres conseils

In answer to the OP's actual question whether this is cryptographically strong, the answer is yes since it is created directly from RNGCryptoServiceProvider. However the currently accepted answer provides a solution that is most definitely not cryptographically secure as per this SO answer:

Is Microsoft's GUID generator cryptographically secure.

Whether this is the correct approach architecturally due to theoretical lack of uniqueness (easily checked with a db lookup) is another concern.

So, what you're building is not technically a GUID. A GUID is a Globally Unique Identifier. You're building a random string of 128 bits. I suggest, like the previous answerer, that you use the built-in GUID generation methods. This method has a (albeit tremendously small) chance of generating duplicate GUID's.

There are a few advantages to using the built-in functionality, including cross-machine uniqueness [partially due to the MAC Address being referenced in the guid, see here: http://en.wikipedia.org/wiki/Globally_Unique_Identifier.

Regardless of whether you use the built in methods, I suggest that you not expose the Purchase GUID to the customer. The standard method used by Microsoft code is to expose a Session GUID that identifies the customer and expires comparatively quickly. Cookies track customer username and saved passwords for session creation. Thus your 'short term purchase ID' is never actually passed to (or, more importantly, received from) the client and there is a more durable wall between your customers' personal information and the Interwebs at large.

Collisions are theoretically impossible (it's not Globally Unique for nothing), but predictability is a whole other question. As Christopher Stevenson correctly points out, given a few previously generated GUIDs it actually becomes possible to start predicting a pattern within a much smaller keyspace than you'd think. GUIDs guarantee uniqueness, not predictability. Most algorithms take it into account, but you should never count on it, especially not as transaction Id for purchases, however briefly. You're creating an open door for brute force session hijacking attacks.

To create a proper unique ID, take some random stuff from your system, append some visitor specific information, and append a string only you know on the server, and then put a good hash algorithm over the whole thing. Hashes are meant to be unpredictable and unreversable, unlike GUIDs.

To simplify: if uniqueness is all you care about, why not just give all your visitors sequential integers, from 1 to infinity. Guaranteed to be unique, just terribly predictable that when you just purchased item 684 you can start hacking away at 685 until it appears.

To avoid collisions:

  1. If you can't keep a global count, then use Guid.NewGuid().
  2. Otherwise, increment some integer and use 1, 2, 3, 4...

"But isn't that ridiculously easy to guess?"

Yes, but accidental and deliberate collisions are different problems with different solutions, best solved separately, note least because predictability helps prevent accidental collision while simultaneously making deliberate collision easier.

If you can increment globally, then number 2 guarantees no collisions. UUIDs were invented as a means to approximate that without the ability to globally track.

Let's say we use incrementing integers. Let's say the ID we have in a given case is 123.

We can then do something like:

private static string GetProtectedID(int id)
{
  using(var sha = System.Security.Cryptography.SHA1.Create())
  {
    return string.Join("", sha.ComputeHash(Encoding.UTF8.GetBytes(hashString)).Select(b => b.ToString("X2"))) + id.ToString();
  }
}

Which produces 09C495910319E4BED2A64EA16149521C51791D8E123. To decode it back to the id we do:

private static int GetIDFromProtectedID(string str)
{
  int chkID;
  if(int.TryParse(str.Substring(40), out chkID))
  {
    string chkHash = chkID.ToString() + "this is my secret seed kjٵتשڪᴻᴌḶḇᶄ™∞ﮟﻑfasdfj90213";
    using(var sha = System.Security.Cryptography.SHA1.Create())
    {
      if(string.Join("", sha.ComputeHash(Encoding.UTF8.GetBytes(hashString)).Select(b => b.ToString("X2"))) == str.Substring(0, 40))
        return chkID;
    }
  }
  return 0;//or perhaps raise an exception here.
}

Even if someone guessed from that they were given number 123, it wouldn't let them deduce that the id for 122 was B96594E536C9F10ED964EEB4E3D407F183FDA043122.

Alternatively, the two could be given as separate tokens, and so on.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top