Generating a unique *and* random URL in C#

2020-06-12 04:21发布

My ultimate goal is to create a URL that is unique and cannot be guessed/predicted. The purpose of this URL is to allow users to perform operations like verifying their email address and resetting their password. These URLs would expire within a set amount of time (currently set to 24 hours).

I was originally using a Guid for this purpose, but I now understand this to be somewhere between "just fine" and "very insecure", depending on which expert you listen to. So, I thought I'd beef up my code a little bit, just in case. At first I thought I'd just stick with using a Guid, but generate it from random bytes rather than the Guid.NewGuid() factory method. Here is the method I came up with:

public static Guid GetRandomGuid()
{
    var bytes = new byte[16];
    var generator = new RNGCryptoServiceProvider();
    generator.GetBytes(bytes);
    return new Guid(bytes);
}

I'm not quite clear on what exactly happens when you use new Guid(bytes) instead of Guid.NewGuid(), but I think all this method does is generate a random byte array and store it in a Guid data structure. In other words, it's no longer guaranteed to be unique, it's only guaranteed to be random.

Since my URLs need to be both unique and random, this does not seem like a solid solution. I'm now thinking, I should base my URLs on a combination of both a unique ID (which could be a Guid or, if available, a database auto-incremented id) and a random sequence generated from RNGCryptoServiceProvider.

Questions

What's the best way to generate a verification/password-reset URL that is both guaranteed unique and extremely difficult/impossible to predict/guess?

  • Should I simply construct my URL by concatenating a unique string with a random string?

  • Does the .NET Framework have a built-in way to easily generate a unique and random (unpredictable) sequence that can be used for URLs?

  • If not, is there a good solution available open source?

Update

In case anyone has a similar requirement, I'm currently using the following method:

public static string GenerateUniqueRandomToken(int uniqueId)
    // generates a unique, random, and alphanumeric token
{
    const string availableChars =
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    using (var generator = new RNGCryptoServiceProvider())
    {
        var bytes = new byte[16];
        generator.GetBytes(bytes);
        var chars = bytes
            .Select(b => availableChars[b % availableChars.Length]);
        var token = new string(chars.ToArray());
        return uniqueId + token;
    }
}

Please comment if you see any problems with this.

5条回答
▲ chillily
2楼-- · 2020-06-12 04:50

Create a table in the database with a linkID and a Datesent column, on generation of the link send insert DateTime.Now into the table and return linkId, set the linkID as a querystring parameter on the activationLink.

On load of the activation page retrive the linkId and use it to evoke a stored procedure that will return the date when passed the corresponding linkId as a parameter, when you get the date back you can add how long you want the link to stay active by using the .AddDays()/.AddMonths (this are C# methods for datetime). Then compare the date you got back with today's date. If it has passed its length of days or months give an error message or else continue and display the page content.

I sugest you keep the contents of the page in a panel and and set it visible = "false" then only make the panel visible="true" if the date is still within range.

查看更多
劫难
3楼-- · 2020-06-12 05:00

[Edit: I missed the call to RNGCryptoServiceProvider above. Apologies about that.]

The problem with RNG generators like the RNGCryptoServiceProvider for your case is they do not guarantee uniqueness. Guids, as you know, are statistically unlikely to be unique but do not guarantee it. The only real way to guarantee both uniqueness and randomness is to generate the GUID like you have then put it in a searchable store, like a database table. Whenever you generate a new value, check it is not already present. If it is, discard it and generate a new value.

查看更多
时光不老,我们不散
4楼-- · 2020-06-12 05:07

A Guid is not meant to be a security feature. The standards for creating a Guid make no claims about how secure it is, or how easy/hard it is to guess. It simply makes a claim about it's uniqueness.

If you attempt to secure your system by using a Guid then you will not have a secure system.

查看更多
【Aperson】
5楼-- · 2020-06-12 05:11

You can generate 128 bit, "random," unique numbers by running a counter through an AES counter keyed with a random key. As long as the same key is used this will never repeat any output.

static byte[] AESCounter(byte[] key, ulong counter) {
    byte[] InputBlock = new byte[16];
    InputBlock[0] = (byte)(counter & 0xffL);
    InputBlock[1] = (byte)((counter & 0xff00L) >> 8);
    InputBlock[2] = (byte)((counter & 0xff0000L) >> 16);
    InputBlock[3] = (byte)((counter & 0xff000000L) >> 24);
    InputBlock[4] = (byte)((counter & 0xff00000000L) >> 32);
    InputBlock[5] = (byte)((counter & 0xff0000000000L) >> 40);
    InputBlock[6] = (byte)((counter & 0xff000000000000L) >> 48);
    InputBlock[7] = (byte)((counter & 0xff00000000000000L) >> 54);
    using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
    {
        AES.Key = key;
        AES.Mode = CipherMode.ECB;
        AES.Padding = PaddingMode.None;
        using (ICryptoTransform Encryptor = AES.CreateEncryptor())
        {
            return Encryptor.TransformFinalBlock(InputBlock, 0, 16);
        }
    }
}
查看更多
乱世女痞
6楼-- · 2020-06-12 05:12

Get md5 of of all the user login credentials and the concatenate it with the guid generated by Guid.NewGuid().ToString() and use it in your url, it should work fine.

查看更多
登录 后发表回答