Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use RNGCryptoServiceProvider to create a random password based on my preferred characters?

What I am currently doing to create a random password is like this:

    public static int getRandomNumber(int maxNumber)
    {
        if (maxNumber < 1)
            throw new System.Exception("The maxNumber value should be greater than 1");
        byte[] b = new byte[4];
        new System.Security.Cryptography.RNGCryptoServiceProvider().GetBytes(b);
        int seed = (b[0] & 0x7f) << 24 | b[1] << 16 | b[2] << 8 | b[3];
        System.Random r = new System.Random(seed);
        return r.Next(1, maxNumber);
    }

Some possible problems? It's a static function that has some strange seeding patterns which may not be secure and still uses System.Random().

Using the above Random Number generator, I create a string, in this inefficient way:

  validCharacters = "abcdefghjkmnoxyz023456789#!@";

then looping through and getting a "createPassword(length)" type string using the valid array (note the use of a character set that doesn't include easily mistaken characters like 1 i etc).

Is this how to do it, or is there an easier, more secure, more effective way?

like image 473
Dexter Avatar asked Mar 11 '14 20:03

Dexter


2 Answers

Your use of System.Random makes asserting security properties very hard because its output is not cryptographically strong. There are only 4 billion passwords now, because there are only 4 billion possible seeds.

Very unsafe. Debian password security was compromised because of a similar problem.

Use RNGCryptoServiceProvider directly to generate totally random ints as input for your password algorithm.

like image 28
usr Avatar answered Nov 15 '22 11:11

usr


For generating the random numbers, you can return the remainder of seed divided by maxNumber:

public static int getRandomNumber(int maxNumber)
{
    if (maxNumber < 1)
        throw new System.Exception("The maxNumber value should be greater than 1");
    byte[] b = new byte[4];
    new System.Security.Cryptography.RNGCryptoServiceProvider().GetBytes(b);
    int seed = (b[0] & 0x7f) << 24 | b[1] << 16 | b[2] << 8 | b[3];
    return seed % maxNumber;
}

maxNumber is exclusive in this case, which is how Random.Next(maxNumber) works too.

EDIT

The comment from @Servy was very interesting and lead me to an article by Stephen Toub and Shawn Farkas titled "Tales from the CryptoRandom" in the September 2007 issue of MSDN Magazine, available for download here, which has an example of reimplementing Random using RNGCryptoServiceProvider that has a work-around for the bias. I've included their implementation here, since the formatting at the source is pretty nasty, but the article is worth reading for the reasoning behind it.

public class CryptoRandom : Random
{
    private RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();
    private byte[] _uint32Buffer = new byte[4];
    public CryptoRandom() { }
    public CryptoRandom(Int32 ignoredSeed) { }
    public override Int32 Next()
    {
        _rng.GetBytes(_uint32Buffer);
        return BitConverter.ToInt32(_uint32Buffer, 0) & 0x7FFFFFFF;
    }
    public override Int32 Next(Int32 maxValue)
    {
        if (maxValue < 0) throw new ArgumentOutOfRangeException("maxValue");
        return Next(0, maxValue);
    }
    public override Int32 Next(Int32 minValue, Int32 maxValue)
    {
        if (minValue > maxValue) throw new ArgumentOutOfRangeException("minValue");
        if (minValue == maxValue) return minValue;
        Int64 diff = maxValue - minValue;
        while (true)
        {
            _rng.GetBytes(_uint32Buffer);
            UInt32 rand = BitConverter.ToUInt32(_uint32Buffer, 0);
            Int64 max = (1 + (Int64)UInt32.MaxValue);
            Int64 remainder = max % diff;
            if (rand < max - remainder)
            {
                return (Int32)(minValue + (rand % diff));
            }
        }
    }
    public override double NextDouble()
    {
        _rng.GetBytes(_uint32Buffer);
        UInt32 rand = BitConverter.ToUInt32(_uint32Buffer, 0);
        return rand / (1.0 + UInt32.MaxValue);
    }
    public override void NextBytes(byte[] buffer)
    {
        if (buffer == null) throw new ArgumentNullException("buffer");
        _rng.GetBytes(buffer);
    }
}
like image 174
Mark Avatar answered Nov 15 '22 11:11

Mark