I'm trying to generate a random string using a range of acceptable characters. I have a working implementation, which is included below, but I wanted to know if the logic of converting the random byte to a printable character exposes me to any risk or inadvertently exposes other internal states. I kept the number of available characters as a number evenly divisible by 256, to help prevent an uneven bias in the generated string.
using System.Security.Cryptography; class Example { static readonly char[] AvailableCharacters = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' }; internal static string GenerateIdentifier(int length) { char[] identifier = new char[length]; byte[] randomData = new byte[length]; using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) { rng.GetBytes(randomData); } for (int idx = 0; idx < identifier.Length; idx++) { int pos = randomData[idx] % AvailableCharacters.Length; identifier[idx] = AvailableCharacters[pos]; } return new string(identifier); } }
Running the above sample code 10 times with a length of 40 gives me the following output:
hGuFJjrr6xuuRDaOzJjaltL-ig09NNzbbvm2CyZG BLcMF-xcKjmFr5fO-yryx8ZUSSRyXcTQcYRp4m1N ARfPJhjENPxxAxlRaMBK-UFWllx_R4nT0glvQLXS 7r7lUVcCkxG4ddThONWkTJq0IOlHzzkqHeMi4ykU TMwTRFORVYCLYc8iWFUbfZWG1Uk2IN35IKvGR0zX hXNADtfnX4sjKdCgmvZUqdaXSFEr_c_mNB3HUcax -3nvJyou8Lc-a0limUUZYRScENOoCoN9qxHMUs9Y bQPmVvsEjx0nVyG0nArey931Duu7Pau923lZUnLp b8DUUu6Rl0VwbH8jVTqkCifRJHCP3o5oie8rFG5J HuxF8wcvHLpiGXedw8Jum4iacrvbgEWmypV6VTh-
The question I guess I'm asking, is this code relatively safe for use or is this a really, really bad idea? The end users never see this identifier and the lifetime is very short lived.
In an attempt to describe more about the use of the identifier, it's intended use is to be used as a key for a short-lived request, used to pass information from one application to another, third-party system. Since the data has to go through the (untrusted) user's browser, we are storing the actual report information in a database and generating this identifier for the target application to be able to pick up and remove that information from the database.
Since the target information is in a third-party system outside of our control (development wise, still on-premises) and we can't directly authenticate our users against the third-party system, this token is intended to allow the user to be identified and for the report to be run with the information stored in the database. The report itself has to be public facing (on the internet) without authentication (because the majority of our users don't have account in the third-party system) and because the report deals with HIPAA/FERPA data we wanted to ensure as best we can that even with the identifier in the attackers control that they can't generate a valid request.
RNGCryptoServiceProvider is marked as obsolete, starting in . NET 6.
The RNGCryptoServiceProvider returns random numbers in the form of bytes, so you need a way to get a more convenient random number from it: public static int GetInt(RNGCryptoServiceProvider rnd, int max) { byte[] r = new byte[4]; int value; do { rnd.GetBytes(r); value = BitConverter.ToInt32(r, 0) & Int32.MaxValue; } ...
Using the random index number, we have generated the random character from the string alphabet. We then used the StringBuilder class to append all the characters together. If we want to change the random string into lower case, we can use the toLowerCase() method of the String .
The additional information is helpful. I presume that you never send the token in the clear and never send it to an untrusted party.
To answer the question that was actually asked: yes, your code correctly generates a 40 character random string containing 240 bits of randomness. I note that of course you consume 320 bits of randomness to do so, but, whatever, bits are cheap.
Presumably the number of tokens thus generated is a very small fraction of 2240, and therefore it will be hard for an attacker to guess at a valid token. If tokens have a short lifespan -- if they are only in the database while the transaction is happening, and then go away a short time later -- that's even better. Defense in depth.
Note that a software RNG takes information from its environment as the seed entropy. If malware can be running on the machine doing the generation then it could be attempting to manipulate that environment, and thereby deduce part of the entropy. But if you have malware running on that machine, odds are good that you already have far larger problems.
I note also that the garbage collector does not make any guarantees about how long those strings and arrays containing the token hang around in memory. Again, if you have malware with admin privileges on your machine that starts up a debugger and interrogates memory, it can discover the keys. Of course that presumes that the bad actors are already on the wrong side of the airtight hatchway, as Raymond Chen says. Memory scanning by malware with admin privileges is the least of your worries!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With