Click here to Skip to main content
15,884,388 members
Articles / Programming Languages / C#
Article

Random Password Generator: an Example

Rate me:
Please Sign up or sign in to vote.
4.67/5 (2 votes)
15 Nov 2017CPOL3 min read 18.2K   411   16   3
using System.Security.Cryptography.RNGCryptoServiceProvider

Intro

When it comes to generating random numbers that requires higher randomness (less predictable/guessable/biased distribution), the popular System.Random is not a good option. Instead, RNGCryptoServiceProvider which (based on the documentation) implements a cryptographic Random Number Generator (RNG), is a recommended choice. For comparison between the two options, pros and cons, and their usages, you can google them. This article is to share my experience working with a project that requires cryptographically secure, and RNGCryptoServiceProvider is my choice.

Notes

A password might contain 4 types of characters:

  1. Lower-case
  2. Upper-case
  3. Numerical
  4. Symbols

Some observations:

  • The 'l' character is sometimes confused with the number 1.
  • Password is usually required to be n characters length. It contains x lower-case, y upper case, z numeric, and t symbol characters (where x + y + z + t = n).

The plan is:

  1. Generate a string of random x lower-case characters
  2. Generate a string of random y upper-case characters
  3. Generate a string of random z numeric characters
  4. Generate a string of random t symbol characters
  5. Add them all together
  6. Shuffle them if needed

The job can be divided into two classes: RandomService (step 1 - 4) and PasswordService (step 5 - 6).

The Code

Determine the set of acceptable characters

First, we need the set of all possible characters for each type of the 4 types above.

Instead of simply defining like this:

C#
public static string _LOWERCASEALPHABETICAL = "abcdefghijklmnopqrstuvwxyz";
public static string _UPPERCASEALPHABETICAL = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static string _NUMERICAL = "0123456789";
public static string _SYMBOL = "@%+!#$^?:.(){}[]~-_`";

We can create a helper method with the ability to filter out some unwanted characters. This will be convenient when we will later need to change which characters to not include:

C#
public static string GetCharRange(char begin, char end, string excludingChars = "")
{
    var result = string.Empty;
    for (char c = begin; c <= end; c++)
    {
        result += c;
    }

    if (!string.IsNullOrEmpty(excludingChars))
    {
        var inclusiveChars = result.Except(excludingChars).ToArray();
        result = new string(inclusiveChars);
    }
    return result;
}

The call below will exclude the 'i', and 'l' from the set of all possible lower-case characters:

C#
public static string _LOWERCASEALPHABETICAL = GetCharRange('a', 'z', "li"); // "abcdefghjkmnopqrstuvwxyz";
public static string _UPPERCASEALPHABETICAL = GetCharRange('A', 'Z'); // "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static string _NUMERICAL = GetCharRange('0', '9'); // "0123456789";
public static string _SYMBOL = "@%+!#$^?:.(){}[]~-_`";

With the above 4 sets of acceptable characters, we need to build 4 random strings that, each contains a number of characters (x, y, z, t). Each chosen character in these random strings is corresponding to an index of that character in the 4 sets above. In other word, the 'random' here is the random index (random number) within the range of index of the char array. To make it clearer, a string is an array of characters. Specifically, random number here is the one generated that is in the range of [0, length - 1].

RandomService class

We design this class to generate random numbers using RNGCryptoServiceProvider:

C#
public static class RandomService
{
    public static readonly RNGCryptoServiceProvider _rngProvider = new RNGCryptoServiceProvider();

    public static int NextInt32()
    {
        var randomBuffer = new byte[4];
        _rngProvider.GetBytes(randomBuffer);
        return BitConverter.ToInt32(randomBuffer, 0);
    }

    // Generate a random real number within range [0.0, 1.0]
    public static double Next()
    {
        var buffer = new byte[sizeof(uint)];
        _rngProvider.GetBytes(buffer); // Fill the buffer
        uint random = BitConverter.ToUInt32(buffer, 0);
        return random / (1.0 + uint.MaxValue);
    }

    // Generate an int within range [min, max - 1] if max > min, and min if min == max
    public static int Next(int min, int max)
    {
        if (min > max) throw new ArgumentException($"Second input ({max}) must be greater than first input ({min}))");
        return (int)(min + (max - min) * Next());
    }

    // Generate an int within range [0, max - 1] if max > 1, and 0 if max == {0, 1}
    public static int Next(int max) => Next(0, max);
}

PasswordService class

Now, we can generate random password by getting the characters from the set of acceptable characters using the generated random numbers (from RandomService class) as the random indexes. The method GetRandomString(string, int) below is to achieve what we were talking about:

C#
public static class PasswordService
{
    public static string _LOWERCASEALPHABETICAL = GetCharRange('a', 'z', "li"); // "abcdefghjkmnopqrstuvwxyz";
    public static string _UPPERCASEALPHABETICAL = GetCharRange('A', 'Z'); // "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    public static string _NUMERICAL = GetCharRange('0', '9'); // "0123456789";
    public static string _SYMBOL = "@%+!#$^?:.(){}[]~-_`";

    public static string RandomUpperCase(int length) => GetRandomString(_UPPERCASEALPHABETICAL, length);
    public static string RandomLowerCase(int length) => GetRandomString(_LOWERCASEALPHABETICAL, length);
    public static string RandomNumerical(int length) => GetRandomString(_NUMERICAL, length);
    public static string RandomSymbol(int length) => GetRandomString(_SYMBOL, length);

    public static string GetCharRange(char begin, char end, string excludingChars = "")
    {
        ... shown above
    }

    private static string GetRandomString(string possibleChars, int len)
    {
        var result = string.Empty;
        for (var position = 0; position < len; position++)
        {
            var index = RandomService.Next(possibleChars.Length);
            result += possibleChars[index];
        }
        return result;
    }

    ...
}

And finally, we generate the password based on required number x, y, z, and t:

C#
public static class PasswordService
{
    ...

    public static string GenerateRandomPassword(int numOfLowerCase, int numOfUpperCase, int numOfNumeric, int numOfSymbol)
    {
        string ordered = RandomLowerCase(numOfLowerCase)
            + RandomUpperCase(numOfUpperCase)
            + RandomNumerical(numOfNumeric)
            + RandomSymbol(numOfSymbol);

        string shuffled = new string(ordered.OrderBy(n => RandomService.NextInt32()).ToArray());

        return shuffled;
    }
}

Notice above that, without shuffling, the password has the characters ordered by type (lower, upper, numeric, or symbol). There are several ways to shuffle, for example, Fisher-Yates algorithm. This program uses a LINQ extension method to sort the elements of the password string based on a random key.

In case of the first character not allowed to be a symbol, there are several ways to walk around this, for example, insert a new random alpha-numeric at the first position after shuffling.

Test the Result

C#
static void GetAndShowPassword(int numOfPass, int lower, int upper, int num, int symbol)
{
    WriteLine($"{numOfPass} generated passwords containing: {lower} lower, {upper} upper, {num} numerical, and {symbol} symbol character(s):");

    for (int i = 0; i < numOfPass; i++)
    {
        string pass = PasswordService.GenerateRandomPassword(lower, upper, num, symbol);
        WriteLine($"\t{pass}");
    }

    WriteLine();
}
C#
static void Main(string[] args)
{
    GetAndShowPassword(4, lower: 2, upper: 2, num: 2, symbol: 2);
    GetAndShowPassword(5, lower: 0, upper: 4, num: 4, symbol: 0);
    GetAndShowPassword(6, lower: 0, upper: 4, num: 4, symbol: 2);
    ReadKey();
}

Image 1

END

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
while (live) {
try {
learn();
code();
food();
...
} catch (Exception ex) {
recover();
}
}

Comments and Discussions

 
GeneralMy vote of 4 Pin
raddevus16-Nov-17 4:12
mvaraddevus16-Nov-17 4:12 
GeneralRe: My vote of 4 Pin
Lộc Nguyễn16-Nov-17 6:32
professionalLộc Nguyễn16-Nov-17 6:32 
Thanks for the comment, raddevus!

I've learned that two recommended things about password:
- unpredictable
- and stored in a way that is 'hard' to reverse back to the original one
My work above is just about creating one that is hard to guess/predict. I've not yet worked on the hasing step. And your article seems to be quite an eye opener for me! Many thanks!

LINQPad, I might try sometimes later. But definitely going to get a copy of 'C# 7.0 in a Nutshell' very soon (I've been reading 'C# and .NET Framework' series by Andrew Troelsen and Philip Japikse, very comprehensive).
GeneralRe: My vote of 4 Pin
raddevus16-Nov-17 7:41
mvaraddevus16-Nov-17 7:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.