A New Password Library in C#

Tags: C#;Security

I recently had a need to generate a password that met a defined policy. I came across several good discussions on Stack Overflow that provided ways to generate cryptographically secure passwords using RNGCryptoServiceProvider to generate truly random bytes. Yet none of the discussions talked about how to implement policies such as having at least one uppercase character, one lowercase character, and one non-alphanumeric character. So I went about building what I needed and I wanted to share it so that others could take it and improve upon it.

You can find the source code here.

User Stories

Here are the user stories that this library satisfies.

  • As a user I want to be able to request a cryptographically secure password. A cryptographically secure password must:
    • Pass statistical randomness test next-bit. Given the first k bits of a random sequence, there is no polynomial-time algorithm that can predict the (k+1)th bit with probability of success non-negligibly better than 50%
    • Hold up well under serious attack. In the event that part or all of its state has been revealed (or guessed correctly), it should be impossible to reconstruct the stream of random numbers prior to the revelation. Additionally, if there is an entropy input while running, it should be infeasible to use knowledge of the input’s state to predict future conditions of the CSPRNG state.
  • As a user I want to be able to specify the length of the password enabling the creation of shorter easier to remember passwords or longer more secure passwords.
  • As a user I want to be able to specify the different character sets required to make up the password.

    Examples

    • Password may only contain numeric characters (0-9) and must be between 4-8 characters in length.
    • Password must contain 1 uppercase (A-Z), 1 lowercase (a-z), and 1 numeric character (0-9) and must be between 12-64 characters in length.

Now that we have defined the user stories let’s look at the library and provide some examples of how to use it.

CharacterSet

CharacterSet is an object that contains a set of allowed characters and the minimum number of those characters required to be in the password for the password to be compliant.

Example

This CharacterSet includes only numeric characters and requires a minimum of 1.

new CharacterSet(1, "1234567890".ToCharArray())

Source Code

<br />public class CharacterSet
{
    public char[] AllowedCharacters { get; }
    public int MinimumNumberRequired { get; }

    public CharacterSet(int minimumNumberRequired, char[] allowedCharacters)
    {
        if (minimumNumberRequired < 0)
        {
            throw new ArgumentOutOfRangeException($"{nameof(minimumNumberRequired)} must be greater than zero.");
        }
        if (allowedCharacters?.Length == 0)
        {
            throw new ArgumentOutOfRangeException($"{nameof(allowedCharacters)} must contain one or more characters.");
        }
        AllowedCharacters = allowedCharacters;
        MinimumNumberRequired = minimumNumberRequired;
    }

}

PasswordPolicy

PasswordPolicy is an object that contains a collection of CharacterSets and the minimum and maximum length of the password.

Example

This PasswordPolicy contains 4 CharacterSets that require the password to contain 1 uppercase (A-Z), 1 lowercase (a-z), 1 numeric character (0-9), and 1 symbol character and must be between 12-64 characters in length.

IEnumerable characterSets = new List()
    {
        new CharacterSet(1, "abcdefghijklmnopqrstuvwxyz".ToCharArray()),
        new CharacterSet(1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()),
        new CharacterSet(1, "1234567890".ToCharArray()),
        new CharacterSet(1, "*\\:,\"()+;/~".ToCharArray()),
    };

PasswordPolicy passwordPolicy = new PasswordPolicy(characterSets, 12, 64);

Source Code

public class PasswordPolicy
{
    public int MinimumLength { get; }
    public int MaximumLength { get; }
    public IEnumerable CharacterSets { get; set; }

    public PasswordPolicy(IEnumerable characterSets, int minimumLength = 8, int maximumLength = 64)
    {
        if (minimumLength < 0)
        {
            throw new ArgumentOutOfRangeException($"{nameof(minimumLength)} must be greater than zero.");
        }
        if (maximumLength < 0)
        {
            throw new ArgumentOutOfRangeException($"{nameof(maximumLength)} must be greater than zero.");
        }
        if (characterSets?.Count() == 0)
        {
            throw new ArgumentOutOfRangeException($"{nameof(characterSets)} must contain one or more CharacterSet.");
        }
        CharacterSets = characterSets;
        MinimumLength = minimumLength;
        MaximumLength = maximumLength;
    }
}

GeneratePassword

GeneratePassword is a method that as the name implies generates a password. There are 3 overloads for this method. The primary signature that will be used takes a desired password length and a PasswordPolicy and returns a password. There are a few things I will point out. The code uses the abstract class RandomNumberGenerator to create a Cryptographically-Secure Pseudo-Random Number Generator (CSPRNG) implementation. In the .NET Framework RandomNumberGenerator.Create() returns an RNGCryptoServiceProvider instance (unless configured differently by CryptoConfig). In .NET Core RandomNumberGenerator.Create() returns an opaque type which is based on BCryptGenRandom (Windows OS) or OpenSSL's random number generator (Non-Windows OS).

The method first generates the necessary characters to meet the CharacterSet requirements. It then joins the CharacterSets together and generates the remainder of the password from the complete list of allowed characters. Once we have a password of appropriate length we call the Shuffle() method to shuffle the characters to ensure that we have a random distribution of the character sets.

Example

IEnumerable characterSets = new List()
    {
        new CharacterSet(1, "abcdefghijklmnopqrstuvwxyz".ToCharArray()),
        new CharacterSet(1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()),
        new CharacterSet(1, "1234567890".ToCharArray()),
        new CharacterSet(1, "*\\:,\"()+;/~".ToCharArray()),
    };

PasswordPolicy passwordPolicy = new PasswordPolicy(characterSets, 12, 64);

string password = Password.GeneratePassword(64, passwordPolicy);

IsPasswordCompliantWithPasswordPolicy

This method takes a password and PasswordPolicy and verifies that the password is compliant with the policy. It returns a boolean.

Example

IEnumerable characterSets = new List()
    {
        new CharacterSet(1, "abcdefghijklmnopqrstuvwxyz".ToCharArray()),
        new CharacterSet(1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()),
        new CharacterSet(1, "1234567890".ToCharArray()),
        new CharacterSet(1, "*\\:,\"()+;/~".ToCharArray()),
    };

PasswordPolicy passwordPolicy = new PasswordPolicy(characterSets, 12, 64);

Console.WriteLine(PasswordPolicy.IsPasswordCompliantWithPasswordPolicy("123456", passwordPolicy));

The example above will output the following:

false

GeneratePasswordPolicyDescription

This method takes a PasswordPolicy and generates a string description of the policy.

Example

IEnumerable characterSets = new List()
    {
        new CharacterSet(1, "abcdefghijklmnopqrstuvwxyz".ToCharArray()),
        new CharacterSet(1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()),
        new CharacterSet(1, "1234567890".ToCharArray()),
        new CharacterSet(1, "*\\:,\"()+;/~".ToCharArray()),
    };

PasswordPolicy passwordPolicy = new PasswordPolicy(characterSets, 12, 64);

Console.WriteLine(PasswordPolicy.GeneratePasswordPolicyDescription(passwordPolicy));

The example above will output the following:

Password must meet the following guidelines:
  * The password must be between 14 and 64 characters long.
  * The password contains characters from the following categories:
    - At least 1 of the following characters (abcdefghijklmnopqrstuvwxyz)
    - At least 1 of the following characters (ABCDEFGHIJKLMNOPQRSTUVWXYZ)
    - At least 1 of the following characters (1234567890)
    - At least 1 of the following characters (*\:,"()+;/~)

Summary

This library was fun to write and I learned quite a bit about cryptography, .NET Standard libraries, and .NET Core. I will cover what I learned about .NET Standard and .NET Core in a future article. I would love feedback on the library and I am happy to review pull requests on ways to improve the library over on GitHub.

Related Links:

One thought on “A New Password Library in C#

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s