I'm using this code to generate random strings with given length
public string RandomString(int length)
{
const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
StringBuilder res = new StringBuilder();
Random rnd = new Random();
while (0 < length--)
{
res.Append(valid[rnd.Next(valid.Length)]);
}
return res.ToString();
}
However, I read that RNGCryptoServiceProvider
is more secure than Random
class. How can I implement RNGCryptoServiceProvider
to this function. It should use valid
string just like this function.
RNGCryptoServiceProvider is marked as obsolete, starting in . NET 6.
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 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; } ...
RNGCryptoServiceProvider generates high-quality random numbers. With it, we use an RNG (random number generator) that is as random as possible. This helps in applications where random numbers must be completely random. Caution: RNGCryptoServiceProvider has a cost: it reduces performance over the Random type.
RNGCryptoServiceProvider is obsolete. To generate a random number, use one of the RandomNumberGenerator static methods instead. Implements a cryptographic Random Number Generator (RNG) using the implementation provided by the cryptographic service provider (CSP).
You could also use modulo in order to not skip the invalid byte values but that the chances for each character won't be even. The RNGCryptoServiceProvider returns random numbers in the form of bytes, so you need a way to get a more convenient random number from it:
To generate a random number, use one of the RandomNumberGenerator static methods instead. Implements a cryptographic Random Number Generator (RNG) using the implementation provided by the cryptographic service provider (CSP). This class cannot be inherited.
Since RNGRandomNumberGenerator only returns byte arrays, you have to do it like this:
static string RandomString(int length)
{
const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
StringBuilder res = new StringBuilder();
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
byte[] uintBuffer = new byte[sizeof(uint)];
while (length-- > 0)
{
rng.GetBytes(uintBuffer);
uint num = BitConverter.ToUInt32(uintBuffer, 0);
res.Append(valid[(int)(num % (uint)valid.Length)]);
}
}
return res.ToString();
}
Note however that this has a flaw, 62 valid characters is equal to 5,9541963103868752088061235991756 bits (log(62) / log(2)), so it won't divide evenly on a 32 bit number (uint).
What consequences does this have? As a result, the random output won't be uniform. Characters which are lower in value will occur more likely (just by a small fraction, but still it happens).
To be more precise, the first 4 characters of a valid array are 0,00000144354999199840239435286 % more likely to occur.
To avoid this, you should use array lengths that will divide evenly into 64 (Consider using Convert.ToBase64String on the output instead, since you can cleanly match 64 bits to 6 bytes.
You need to generate random byte
s using RNGCryptoServiceProvider
and append only the valid ones to the returned string
:
const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
static string GetRandomString(int length)
{
string s = "";
using (RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider())
{
while (s.Length != length)
{
byte[] oneByte = new byte[1];
provider.GetBytes(oneByte);
char character = (char)oneByte[0];
if (valid.Contains(character))
{
s += character;
}
}
}
return s;
}
You could also use modulo in order to not skip the invalid byte
values but that the chances for each character won't be even.
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;
} while (value >= max * (Int32.MaxValue / max));
return value % max;
}
Then you can use that in your method:
public static string RandomString(int length) {
const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
StringBuilder res = new StringBuilder();
using (RNGCryptoServiceProvider rnd = new RNGCryptoServiceProvider()) {
while (length-- > 0) {
res.Append(valid[GetInt(rnd, valid.Length)]);
}
}
return res.ToString();
}
(I made the method static, as it doesn't use any instance data.)
I am aware of the deviation with OP's use case, but I think this might help others who does not have the "arbitrary" 62 character limitation and just want to encode bytes using RNGCryptoServiceProvider to generate random string.
just skip to the bottom, the base64 encoded case.
A lot can be said about the reasons to convert a cryptographic byte array into a string, but usually it is for some sort of serialization purposes; and hence, in that case: the selected character set is arbitrary.
So, if it is about serialization, you have tons of options; e.g:
All of these are making use of the same thing: encode numbers in such a way it is suited to be transmitted in a medium that does not support native binary transfer.
I call this "text as ... representation", because in the end, it is text that will be transmitted.
An example in HEX:
//note: using text as HEX makes the result longer
var crypt = new RNGCryptoServiceProvider();
var sb = new StringBuilder();
var buf = new byte[10]; //length: should be larger
crypt.GetBytes(buf);
//gives a "valid" range of: "0123456789ABCDEF"
foreach (byte b in buf)
sb.AppendFormat("{0:x2}", b); //applies "text as hex" encoding
//sb contains a RNGCryptoServiceProvider based "string"
Now you'll say:
but wait: these are only 16 characters where OP's sequence has 62. 62 is more efficient than 16, so, converted to text, your string will be a lot longer.
"Yes", I'll say, "and if that's a problem, why don't you pick a larger number easy-to-read-and-serrializable-characters... 62 ... or 64 perhaps"
The code would be:
//note: added + and / chars. could be any of them
const string valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+/";
var crypt = new RNGCryptoServiceProvider();
var sb = new StringBuilder();
var buf = new byte[10]; //length: should be larger
crypt.GetBytes(buf); //get the bytes
foreach (byte b in buf)
sb.Append(valid[b%64]);
Note: As @Guffa stated; using %
is forbidden unless it doesn't alter the distribution. To make this happen, given a evenly distributed set, the subset must fit exactly x times in the original set.
So, expanding your initial valid set with 2 gives a valid result (because: 256 / 64 = 4) --- but, this does not honor OP's 62 character requirements. In fact to get an even distribution you'll need some trickery, addressed in the other answers.
Also note: in all the answers, including this one, the sub-set is smaller than the 256 possibilities of the byte. This means there is less information available in an encoded char than in a byte. This means if you have your string with 4 encoded chars, it's easier to crack the original 4 byte result of the RNGCryptoServiceProvider - So keep in mind, the cryptographic strength is depending on the byte length, not the encoded char length.
But, now you say:
"Ok, let drop the 62 requirement, and use 64 - why not use 64 base encoding?",
well, if it's suits you, but note trailing =
, see Base64 on Wikipedia, it is an additional optional charater which is used.
var crypt = new RNGCryptoServiceProvider();
// = padding characters might be added to make the last encoded block
// contain four Base64 characters.
// which is actually an additional character
var buf = new byte[10];
crypt.GetBytes(buf);
//contains a RNGCryptoServiceProvider random string, which is fairly readable
//and contains max 65 different characters.
//you can limit this to 64, by specifying a different array length.
//because log2(64) = 6, and 24 = 4 x 6 = 3 x 8
//all multiple of 3 bytes are a perfect fit. (e.g.: 3, 6, 15, 30, 60)
string result = Convert.ToBase64String(buf);
My implementation that fixes the issue with 5,9541963103868752088061235991756 bits
public static string RandomString(int length)
{
const string alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
var res = new StringBuilder(length);
using (var rng = new RNGCryptoServiceProvider())
{
int count = (int)Math.Ceiling(Math.Log(alphabet.Length, 2) / 8.0);
Debug.Assert(count <= sizeof(uint));
int offset = BitConverter.IsLittleEndian ? 0 : sizeof(uint) - count;
int max = (int)(Math.Pow(2, count*8) / alphabet.Length) * alphabet.Length;
byte[] uintBuffer = new byte[sizeof(uint)];
while (res.Length < length)
{
rng.GetBytes(uintBuffer, offset, count);
uint num = BitConverter.ToUInt32(uintBuffer, 0);
if (num < max)
{
res.Append(alphabet[(int) (num % alphabet.Length)]);
}
}
}
return res.ToString();
}
see https://bitbucket.org/merarischroeder/number-range-with-no-bias/
I'm sure I have answered this one before with a secure implementation, no bias, and good performance. If so, please comment.
Looking at Tamir's answer, I thought it would be better to use the modulus operation, but trim off the incomplete remainder of byte values. I'm also writing this answer now (possibly again), because I need to reference this solution to a peer.
Approach 1
if (buffer[i] >= exclusiveLimit)
Code for Approach 1
const string lookupCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
static void TestRandomString()
{
Console.WriteLine("A random string of 100 characters:");
int[] randomCharacterIndexes = new int[100];
SecureRangeOriginal(randomCharacterIndexes, lookupCharacters.Length);
var sb = new StringBuilder();
for (int i = 0; i < randomCharacterIndexes.Length; i++)
{
sb.Append(lookupCharacters[randomCharacterIndexes[i]]);
}
Console.WriteLine(sb.ToString());
Console.WriteLine();
}
static void SecureRangeOriginal(int[] result, int maxInt)
{
if (maxInt > 256)
{
//If you copy this code, you can remove this line and replace it with `throw new Exception("outside supported range");`
SecureRandomIntegerRange(result, 0, result.Length, 0, maxInt); //See git repo for implementation.
return;
}
var maxMultiples = 256 / maxInt; //Finding the byte number boundary above the provided lookup length - the number of bytes
var exclusiveLimit = (maxInt * maxMultiples); //Expressing that boundary (number of bytes) as an integer
var length = result.Length;
var resultIndex = 0;
using (var provider = new RNGCryptoServiceProvider())
{
var buffer = new byte[length];
while (true)
{
var remaining = length - resultIndex;
if (remaining == 0)
break;
provider.GetBytes(buffer, 0, remaining);
for (int i = 0; i < remaining; i++)
{
if (buffer[i] >= exclusiveLimit)
continue;
var index = buffer[i] % maxInt;
result[resultIndex++] = index;
}
}
}
}
Approach 2
Results:
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