Click here to Skip to main content
15,868,154 members
Articles / Desktop Programming / Windows Forms

WinForm Generate Password Tool

Rate me:
Please Sign up or sign in to vote.
4.27/5 (3 votes)
24 Sep 2019CPOL6 min read 8.3K   415   10  
WinForm Generate Password Tool

Background Table of Contents

I was recently working on a WinForm application that

  1. Requires users and administrators to sign in with a username and password.
  2. Allows an administrator to add users and assign them passwords.
  3. Allows users and administrators to reset their passwords online.

I researched the internet for a password generator and found nothing to my liking. So I decided to roll my own. This article describes the resulting product.

Introduction Table of Contents

Generate Password

I needed a form, in this case a WinForm, that would allow users and administrators to generate passwords. The results took on the appearance of the form to the left.

If readers note a resemblance to the LastPass [^] Generate Secure Password tool, they would be right, since that tool formed the basis for the design of the Generate Password tool, presented here.

I approached LastPass to determine if they had, or were planning to have, a password generating API. I was advised that the idea was in their features list.


Implementation Table of Contents

During design, I realized that I needed three components: a password generator, a means to compute the strength of the password, and a means of inter-form communication.

Password Generator Table of Contents

I found a password generator in kitsu.eb's answer in Generating Random Passwords [^]. Modifications were required to honor the user's choices to

  1. Specify the character classes (uppercase, lowercase, numeric, special) to be used to generate the password.
  2. Limit the contents of the character classes based on criteria such as "easy to say" or "easy to read".

The default character classes are:

C#
const string  DIGITS = "0123456789";
const string  LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
const string  SPECIAL = @"!@#$%^&*()+=~[:'<>?,.|";
const string  UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Generate Password

The first step in generating a new password is to modify copies of the default character sets so as to reflect user desires and then to populate a computationally efficient data structure to support the actual password generation. The create_character_sets method performs these functions.

If a particular character set is desired (as a result of the user choosing one or more of the "Characters to Include" checkboxes), the default character set is placed into a copy of the default character set; if not chosen, the copy is set to the empty string.

The user's further restrictions are then applied. In the tool, there are three restrictions.

  1. All characters - no modifications are made to the copies of the character sets.
  2. Easy to say - the digits and special character sets are removed (the copies are set to the empty string).
  3. Easy to read - the ambiguous characters 01OIoli!| are removed from the copies.

When the user's restrictions have been applied, non zero-length copies are added to the character_sets List.

C#
List < string > character_sets = new List < string > ( );

If a copy has a length of zero that character class will not participate in the password generation and its copy will not be added to the character_sets List. If the copy has been modified by the user applying restrictions, then that reduced character class will be added to the character_sets List.

C#
// ************************************* create_character_sets

/// <summary>
/// creates a data structure that will contain the character
/// classes that will take part in password generation
/// </summary>
/// <param name="all_characters">
/// use any character combination in the password
/// </param>
/// <param name="easy_to_read">
/// avoid ambiguous characters in the password
/// </param>
/// <param name="easy_to_say">
/// avoid numbers and special characters in the password
/// </param>
/// <param name="lower_case">
/// include lowercase characters in the password
/// </param>
/// <param name="numbers">
/// include numeric characters in the password
/// </param>
/// <param name="symbols">
/// include special characters in the password
/// </param>
/// <param name="upper_case">
/// include uppercase characters in the password
/// </param>
/// <returns>
/// a list of strings containing character classes that are to
/// be used to generate a password
/// </returns>
List < string > create_character_sets ( bool  all_characters,
                                        bool  easy_to_read,
                                        bool  easy_to_say,
                                        bool  lower_case,
                                        bool  numbers,
                                        bool  symbols,
                                        bool  upper_case )
    {
    string          digits = String.Empty;;
    List < string > list = new List < string > ( );
    string          lowercase = String.Empty;
    string          special = String.Empty;
    string          uppercase = String.Empty;

    if ( numbers )              // does user want digits?
        {
        digits = DIGITS;
        }

    if ( lower_case )           // does user want lowercase?
        {
        lowercase = LOWERCASE;
        }

    if ( symbols )              // does user want symbols?
        {
        special = SPECIAL;
        }

    if ( upper_case )           // does user want uppercase?
        {
        uppercase = UPPERCASE;
        }
                                // all_characters need not be
                                // tested because if it was
                                // true, no changes would be
                                // made
    if ( easy_to_say )
        {
        digits = String.Empty;
        special = String.Empty;
        }
    else if ( easy_to_read )
        {
                                // remove the ambiguous
                                // characters 01OIoli!|
        if ( !String.IsNullOrEmpty ( digits ) )
            {
            digits = digits.Replace ( "0", String.Empty ).
                            Replace ( "1", String.Empty );
            }

        if ( !String.IsNullOrEmpty ( uppercase ) )
            {
            uppercase = uppercase.
                            Replace ( "O", String.Empty ).
                            Replace ( "I", String.Empty );
            }

        if ( !String.IsNullOrEmpty ( lowercase ) )
            {
            lowercase = lowercase.
                            Replace ( "o", String.Empty ).
                            Replace ( "l", String.Empty ).
                            Replace ( "i", String.Empty );
            }

        if ( !String.IsNullOrEmpty ( special ) )
            {
            special = special.Replace ( "!", String.Empty ).
                              Replace ( "|", String.Empty );
            }
        }
                                // generate a computationally
                                // efficient data structure
    list.Clear ( );
                                // if a character class' copy
                                // has a non-zero length, add
                                // it to the List
    if ( uppercase.Length > 0 )
        {
        list.Add ( uppercase );
        }

    if ( lowercase.Length > 0 )
        {
        list.Add ( lowercase );
        }

    if ( digits.Length > 0 )
        {
        list.Add ( digits );
        }

    if ( special.Length > 0 )
        {
        list.Add ( special );
        }

    return ( list );

    } // create_character_sets

With the character classes revised, the actual password generation can commence. Note that, in addition to the user-specified characters to be used, password generation depends upon the user-specified desired length. generate_password is the method that actually generates the password.

Modifications to the source algorithm were needed to make it responsive to user choices. The original algorithm created passwords that were made up of upper- and lower-case letters, digits, and special characters. Although quite acceptable, this approach could not honor a set of user choices.

The major change was to incorporate the character_sets data structure. The structure was passed to generate_password as a parameter. Recall that the character_sets strucure contains only those character classes that should participate in the generation of a password.

C#
// ***************************************** generate_password

/// <summary>
/// generate a cryptographically strong password of the
//  desired length using the specified character sets
/// </summary>
/// <param name="character_sets">
/// List < string > containing one or more characters sets
/// that are to be used to generate the password
/// </param>
/// <param name="desired_length">
/// desired length of the password
/// </param>
/// <returns>
/// string containing a cryptographically strong password
/// </returns>
string generate_password ( List < string > character_sets,
                           int             desired_length )
    {
    byte [ ]        bytes;
    string          characters;
    int             index = -1;
    StringBuilder   sb = new StringBuilder ( );
                                // get a cryptographically
                                // strong sequence of random
                                // bytes
    bytes = new byte [ desired_length ];
    new RNGCryptoServiceProvider ( ).GetBytes ( bytes );

    foreach ( byte b in bytes )
        {
                                // randomly select a character
                                // class for each byte
        index = random.Next ( character_sets.Count );
        characters = character_sets [ index ];
                                // use mod to project byte b
                                // into the correct range
        sb.Append ( characters [ b % characters.Length ] );
        }

    return ( sb.ToString ( ) );

    } // generate_password

In generate_password, the variable random was previously declared globally as:

C#
using System;
:
:
        private readonly static Random random = new Random ( );

and RNGCryptoServiceProvider was instantiated by

C#
using System.Security.Cryptography;

The unnecessary variable characters contains an intermediate result for readability. I believe the alternative code is unintelligible.

Computing the password strength Table of Contents

I searched the internet for a method that determined the strength of a password. The one that I found to be most intelligent was Password Strength Control [^]. Major modifications were required. The author had included a DataTable that was apparently used for debugging and statistical support. The DataTable and all references to it were removed.

Since the algorithm must examine each character in the generated password, some attention was given to the efficiency of statements within the loop. As a result, the code to the left was replaced by the code to the right.

cd
if (Char.IsDigit(ch))          if ( Char.IsDigit ( current_character ) )
    {                              {
    iDigit++;                      digit_count++;
                                   current_type = Types.DIGIT;
    if (ConsecutiveMode == 3)      }
        iConsecutiveDigit++;
    ConsecutiveMode = 3;
    }

This change was made for all character sets (uppercase, lowercase, and special).

To differentiate between character types, an enum defining the types and an array to contain the counts of consecutive types were declared. Then the array was initialized.

C#
        enum Types
            {
            NOT_SPECIFIED,
            SYMBOL,
            DIGIT,
            UPPERCASE,
            LOWERCASE,
            NUMBER_TYPES
            }
:
:
            int [ ]    consecutives;
:
:
            consecutives = new int [ ( int ) Types.NUMBER_TYPES ];
            for ( int i = ( int ) Types.NOT_SPECIFIED;
                    ( i < ( int ) Types.NUMBER_TYPES );
                      i++ )
                {
                consecutives [ i ] = 0;
                }

This allowed the following statements:

C#
if ( current_type == prior_type )
    {
    types [ ( int ) current_type ]++;
    }

prior_type = current_type;

effectively eliminating a significant amount of code. Various other tests were performed finally yielding a password strength method.

Lastly, the test for digits or symbols in the middle of the password was eliminated.

C#
// ***************************************** password_strength

/// <summary>
/// Determines how strong a password is based on different
/// criteria; 0 is very weak and 100 is very strong
///
/// Concept from
///
/// https://www.codeproject.com/script/Articles/
///     ViewDownloads.aspx?aid=59186
///
/// which has been significantly modified
/// </summary>
/// <param name="password">
/// string containing the password whose strength is to be
/// determined
/// </param>
/// <returns>
/// integer containing the strength of the password; in the
/// range  [ 0 - 100 ]
/// </returns>
int password_strength ( string password )
    {
    int [ ]    consecutives;
    Types      current_type = Types.NOT_SPECIFIED;
    int        digit_count = 0;
    int        lowercase_count = 0;
    int        password_length = password.Length;
    Types      prior_type = Types.NOT_SPECIFIED;
    Hashtable  repeated = new Hashtable();
    int        repeated_count = 0;
    int        requirments = 0;
    int        running_score = 0;
    int        sequential_alphabetic_count = 0;
    int        sequential_number_count = 0;
    int        symbol_count = 0;
    int        uppercase_count = 0;

    consecutives = new int [ ( int ) Types.NUMBER_TYPES ];
    for ( int i = ( int ) Types.NOT_SPECIFIED;
            ( i < ( int ) Types.NUMBER_TYPES );
              i++ )
        {
        consecutives [ i ] = 0;
        }
                                // scan password
    foreach ( char current_character in password.
                                            ToCharArray ( ) )
        {
                                // count digits
        if ( Char.IsDigit ( current_character ) )
            {
            digit_count++;
            current_type = Types.DIGIT;
            }
                                // count uppercase characters
        else if ( Char.IsUpper ( current_character ) )
            {
            uppercase_count++;
            current_type = Types.UPPERCASE;
            }
                                // count lowercase characters
        else if ( Char.IsLower ( current_character ) )
            {
            lowercase_count++;
            current_type = Types.LOWERCASE;
            }
                                // count symbols
        else if ( Char.IsSymbol ( current_character ) ||
                  Char.IsPunctuation ( current_character ) )
            {
            symbol_count++;
            current_type = Types.SYMBOL;
            }

        if ( current_type == prior_type )
            {
            consecutives [ ( int ) current_type ]++;
            }

        prior_type = current_type;

                                // count repeated letters
        if ( Char.IsLetter ( current_character ) )
            {
            if ( repeated.Contains ( Char.ToLower (
                                     current_character ) ) )
                {
                repeated_count++;
                }
            else
                {
                repeated.Add ( Char.ToLower (
                                    current_character ),
                               0 );
                }
            }

        }
                                // check for sequential alpha
                                // string patterns (forward
                                // and reverse)
    for ( int i = 0; ( i < 23 ); i++ )
        {
        string  forward = LOWERCASE.Substring ( i, 3 );
        string  reverse = reverse_string ( forward );

        if ( ( password.ToLower ( ).
                        IndexOf ( forward ) != -1 ) ||
             ( password.ToLower ( ).
                        IndexOf ( reverse ) != -1 ) )
            {
            sequential_alphabetic_count++;
            }
        }
    for ( int i = 0; ( i < 8 ); i++)
        {
        string  forward = DIGITS.Substring ( i, 3 );
        string  reverse = reverse_string ( forward );

        if ( ( password.ToLower ( ).
                        IndexOf ( forward ) != -1 ) ||
             ( password.ToLower ( ).
                        IndexOf ( reverse ) != -1 ) )
            {
            sequential_number_count++;
            }
        }
                                // ADDITIONS TO STRENGTH

    running_score = ( ( 4 * password_length ) +
                      ( 2 * ( password_length -
                             uppercase_count ) ) +
                      ( 2 * ( password_length -
                             lowercase_count ) ) +
                      ( 4 * digit_count ) +
                      ( 6 * symbol_count ) );
                                // requirments
    requirments = 0;

    if ( password_length >=
         MINIMUM_PASSWORD_CHARACTERS )
        {
        requirments++;
        }
    if ( uppercase_count > 0 )
        {
        requirments++;
        }
    if ( lowercase_count > 0 )
        {
        requirments++;
        }
    if ( digit_count > 0 )
        {
        requirments++;
        }
    if ( symbol_count > 0 )
        {
        requirments++;
        }

    if ( requirments > 3 )
        {
        running_score += ( 2 * requirments );
        }

                                // DEDUCTIONS FROM STRENGTH

                                // if only letters
    if ( ( digit_count == 0 ) && ( symbol_count == 0 ) )
        {
        running_score -= password_length;
        }
                                // if only digits
    if ( digit_count == password_length )
        {
        running_score -= password_length;
        }
                                // if repeated letters
    if ( repeated_count > 1 )
        {
        running_score -= ( repeated_count *
                           ( repeated_count - 1 ) );
        }

    for ( int i = 0; ( i < ( int ) Types.NUMBER_TYPES ); i++ )
        {
        running_score -= ( 2 * consecutives [ i ] );
        }

    running_score -= sequential_alphabetic_count;
    running_score -= sequential_number_count;
                                // confine result to range
    if ( running_score > 100 )
        {
        running_score = 100;
        }
    else if ( running_score < 0 )
        {
        running_score = 0;
        }

    return ( running_score );

    } // password_strength

Wrapping it up Table of Contents

Parts of the GeneratePassword.cs are specifically designed to provide a user interface for the tool. I have isolated the invocations of various methods to the regenerate_password. This method is a wrapper around the invocations of the other methods used to populate the user interface.

C#
// *************************************** regenerate_password

/// <summary>
/// regenerate_password is a wrapper around the three major
/// password generation calculate and display methods:
///
///     create_character_sets
///     generate_password
///     password_strength
///
/// in addition it places the generated password into the
/// globally known class named GenerateGlobals
/// </summary>
void regenerate_password ( )
    {
    List < string > character_sets = new List < string > ( );
    string          generated_password = String.Empty;
    int             strength = 0;

    character_sets = create_character_sets ( all_characters,
                                             easy_to_read,
                                             easy_to_say,
                                             lower_case,
                                             numbers,
                                             symbols,
                                             upper_case );

    generated_password = generate_password ( character_sets,
                                             desired_length );

    strength = password_strength ( generated_password );

    password_strength_PB.Value = strength;
    strength_LAB.Text = strength_in_words ( strength );

    GG.generated_password = generated_password;
    generated_password_TB.Clear ( );
    generated_password_TB.Text = generated_password;

    } // regenerate_password

Inter-form Communication Table of Contents

There are a number of ways by which a form can communicate values to another form:

  • Event Handlers
  • Properties
  • Reflection
  • Shared classes

I decided a to implement communication through a globally known class named GenerateGlobals. The using alias directive [^] used in both the Generate Password tool and its invoking form might be:

C#
using GG = PasswordGeneration.GenerateGlobals;

The PasswordGeneration namespace directory is:

Generate Password Module
PasswordGeneration
  GenerateGlobals
  GenerateHelp
  GeneratePassword
  generate_help_text
  generate_icon
  generate_image
  help_image
  regenerate_image
  RichTextBoxExtensions
  TransparentLabel

As shown towards the end of regenerate_password, the value of the newly generated password is placed into generated_password in the GenerateGlobals class. Any form that instantiated the Generated Password form, can retrieve it from there.


Testing the Implementation Table of Contents

Test Generate Password

To test the Generate Password tool, a harness was developed named TestPasswordGeneration. Its purposes were to trigger the Generate Password tool and display the password that was acceptable to the user.

Test Generate Password

Because communication between forms was required, I used the global class named GenerateGlobals.

The following code causes the form to appear and then optionally display the newly generated password. It is the event handler for the Generate button Click event.

C#
generate_password_form = new PasswordGeneration.GeneratePassword ( );
if ( ( ( PasswordGeneration.GeneratePassword ) 
            generate_password_form ).initialize_form ( ) )
    {
                                      // use modal (ShowDialog) so 
                                      // that the generated password 
                                      // can be captured
    generate_password_form.ShowDialog ( );
    if ( !String.IsNullOrEmpty ( GG.generated_password ) )
        {
        generated_password = GG.generated_password;
        generated_password_TB.Clear ( );
        generated_password_TB.Text = generated_password;
        generated_password_TB.Visible = true;
        }
    }

References Table of Contents

Conclusion Table of Contents

I have presented a WinForm that allows developers to provide administrators and users with the ability to generate passwords. I am considering developing the same functionality for WebForms.

Development Environment Table of Contents

The Generate Password tool was developed in the following environment:

Microsoft Windows 7 Professional SP 1
Microsoft Visual Studio 2008 Professional SP1
Microsoft Visual C# 2008
Microsoft .Net Framework Version 3.5 SP1
Microsoft Office PowerPoint 2003

History Table of Contents

09/20/2019 Original article

License

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


Written By
Software Developer (Senior)
United States United States
In 1964, I was in the US Coast Guard when I wrote my first program. It was written in RPG (note no suffixing numbers). Programs and data were entered using punched cards. Turnaround was about 3 hours. So much for the "good old days!"

In 1970, when assigned to Washington DC, I started my MS in Mechanical Engineering. I specialized in Transportation. Untold hours in statistical theory and practice were required, forcing me to use the university computer and learn the FORTRAN language, still using punched cards!

In 1973, I was employed by the Norfolk VA Police Department as a crime analyst for the High Intensity Target program. There, I was still using punched cards!

In 1973, I joined Computer Sciences Corporation (CSC). There, for the first time, I was introduced to a terminal with the ability to edit, compile, link, and test my programs on-line. CSC also gave me the opportunity to discuss technical issues with some of the brightest minds I've encountered during my career.

In 1975, I moved to San Diego to head up an IR&D project, BIODAB. I returned to school (UCSD) and took up Software Engineering at the graduate level. After BIODAB, I headed up a team that fixed a stalled project. I then headed up one of the two most satisfying projects of my career, the Automated Flight Operations Center at Ft. Irwin, CA.

I left Anteon Corporation (the successor to CSC on a major contract) and moved to Pensacola, FL. For a small company I built their firewall, given free to the company's customers. An opportunity to build an air traffic controller trainer arose. This was the other most satisfying project of my career.

Today, I consider myself capable.

Comments and Discussions

 
-- There are no messages in this forum --