Click here to Skip to main content
Licence 
First Posted 5 Jun 2002
Views 257,682
Bookmarked 121 times

A C# Password Generator

By | 17 Aug 2003 | Article
An article on implementing a simple password generator class in C#

Introduction

This article illustrates how to create a very simple password generator using C#. Password generators are useful in many applications:

  • Registration/membership systems for Web sites
  • Auto-creation of passwords according to a specified rule
  • Securing application-specific data

The PasswordGenerator class is fairly simple. It exposes several properties that control how the password will be generated.

  • Exclusions: Specifies the set of characters to exclude in password generation.
  • Minimum: Specifies the minimum length of the generated password.
  • Maximum: Specifies the maximum length of the generated password.
  • ConsecutiveCharacters: Controls generation of consecutive characters in the generated password.
  • RepeatingCharacters: Controls generation of repeating characters in the generated password.
  • ExcludeSymbols: Excludes symbols from the set of characters used to generate the password

After setting your desired properties, call the Generate() method to create your new password.

namespace WorkingCode.CodeProject.PwdGen
{
    using System;
    using System.Security.Cryptography;
    using System.Text;

    public class PasswordGenerator
    {
        public PasswordGenerator() 
        {
            this.Minimum               = DefaultMinimum;
            this.Maximum               = DefaultMaximum;
            this.ConsecutiveCharacters = false;
            this.RepeatCharacters      = true;
            this.ExcludeSymbols        = false;
            this.Exclusions            = null;

            rng = new RNGCryptoServiceProvider();
        }		
		
        protected int GetCryptographicRandomNumber(int lBound, int uBound)
        {   
            // Assumes lBound >= 0 && lBound < uBound
            // returns an int >= lBound and < uBound
            uint urndnum;   
            byte[] rndnum = new Byte[4];   
            if (lBound == uBound-1)  
            {
                // test for degenerate case where only lBound can be returned
                return lBound;
            }
                                                              
            uint xcludeRndBase = (uint.MaxValue -
                (uint.MaxValue%(uint)(uBound-lBound)));   
            
            do 
            {      
                rng.GetBytes(rndnum);      
                urndnum = System.BitConverter.ToUInt32(rndnum,0);      
            } while (urndnum >= xcludeRndBase);   
            
            return (int)(urndnum % (uBound-lBound)) + lBound;
        }

        protected char GetRandomCharacter()
        {            
            int upperBound = pwdCharArray.GetUpperBound(0);

            if ( true == this.ExcludeSymbols )
            {
                upperBound = PasswordGenerator.UBoundDigit;
            }

            int randomCharPosition = GetCryptographicRandomNumber(
                pwdCharArray.GetLowerBound(0), upperBound);

            char randomChar = pwdCharArray[randomCharPosition];

            return randomChar;
        }
        
        public string Generate()
        {
            // Pick random length between minimum and maximum   
            int pwdLength = GetCryptographicRandomNumber(this.Minimum,
                this.Maximum);

            StringBuilder pwdBuffer = new StringBuilder();
            pwdBuffer.Capacity = this.Maximum;

            // Generate random characters
            char lastCharacter, nextCharacter;

            // Initial dummy character flag
            lastCharacter = nextCharacter = '\n';

            for ( int i = 0; i < pwdLength; i++ )
            {
                nextCharacter = GetRandomCharacter();

                if ( false == this.ConsecutiveCharacters )
                {
                    while ( lastCharacter == nextCharacter )
                    {
                        nextCharacter = GetRandomCharacter();
                    }
                }

                if ( false == this.RepeatCharacters )
                {
                    string temp = pwdBuffer.ToString();
                    int duplicateIndex = temp.IndexOf(nextCharacter);
                    while ( -1 != duplicateIndex )
                    {
                        nextCharacter = GetRandomCharacter();
                        duplicateIndex = temp.IndexOf(nextCharacter);
                    }
                }

                if ( ( null != this.Exclusions ) )
                {
                    while ( -1 != this.Exclusions.IndexOf(nextCharacter) )
                    {
                        nextCharacter = GetRandomCharacter();
                    }
                }

                pwdBuffer.Append(nextCharacter);
                lastCharacter = nextCharacter;
            }

            if ( null != pwdBuffer )
            {
                return pwdBuffer.ToString();
            }
            else
            {
                return String.Empty;
            }	
        }
            
        public string Exclusions
        {
            get { return this.exclusionSet;  }
            set { this.exclusionSet = value; }
        }

        public int Minimum
        {
            get { return this.minSize; }
            set	
            { 
                this.minSize = value;
                if ( PasswordGenerator.DefaultMinimum > this.minSize )
                {
                    this.minSize = PasswordGenerator.DefaultMinimum;
                }
            }
        }

        public int Maximum
        {
            get { return this.maxSize; }
            set	
            { 
                this.maxSize = value;
                if ( this.minSize >= this.maxSize )
                {
                    this.maxSize = PasswordGenerator.DefaultMaximum;
                }
            }
        }

        public bool ExcludeSymbols
        {
            get { return this.hasSymbols; }
            set	{ this.hasSymbols = value;}
        }

        public bool RepeatCharacters
        {
            get { return this.hasRepeating; }
            set	{ this.hasRepeating = value;}
        }

        public bool ConsecutiveCharacters
        {
            get { return this.hasConsecutive; }
            set	{ this.hasConsecutive = value;}
        }

        private const int DefaultMinimum = 6;
        private const int DefaultMaximum = 10;
        private const int UBoundDigit    = 61;

        private RNGCryptoServiceProvider    rng;
        private int 			minSize;
        private int 			maxSize;
        private bool			hasRepeating;
        private bool			hasConsecutive;
        private bool            hasSymbols;
        private string          exclusionSet;
        private char[] pwdCharArray = "abcdefghijklmnopqrstuvwxyzABCDEFG" +
            "HIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_=+[]{}\\|;:'\",<" + 
            ".>/?".ToCharArray();                                        
    }
}

The previous version of this class was intended for use in a much larger project (I will be publishing articles on various components of that project soon.). Due to many factors, that code was rushed and proved to be inefficient. In fact, I wish I could go back in time and unpublish it! While this version is definitely better, there is still room for improvement. The generation algorithm can still be optimized. Also, it would be interesting to use regular expressions to both define and validate the passwords we wish to generate. I would have done this, but it's been a long time since I wrote a parser. Maybe for the next version...

In previous articles, I have used the NAnt tool as my build solution. Unfortunately, that team has not produced a stable release that integrates NUnit 2.0. I COULD get the source from the CVS tree, but I'm way too lazy for that. Instead, I have decided to go back to Visual Studio .NET as my development environment. I'm also getting used to test-driven development with NUnit 2.0. If you aren't using this tool for unit testing, I highly recommend you give it a try http://www.nunit.org/. Its use of attributes and reflection to specify test suites, test fixtures and tests, and is quite remarkable and easy to use. I've included my unit test fixture with the source code. Also, try the NUnit Addin for Visual Studio .NET; it's very handy for running your tests within the IDE.

The demo project is a simple Windows Forms UI that allows one to configure the password generator's properties. I must say that while VS.NET is fairly complete and powerful, I just don't like the feel of the forms designer. However, it definitely does the job.

Many thanks to Mike Asher and Julian Roberts for their feedback on the first version of the password generator. Julian was kind enough to test the code in an ASP.NET project and confirmed that it performs much better. Also, I reverted to my old C++ bracing style just to make Nish happy...hope you appreciate the sacrifice! :-)

Change Log

Version 1.2

  • Updated for .NET Framework 1.1
  • Removed FirstCharacter and LastCharacter properties; Exclusions works across all characters
  • Replaced Password property with Generate() method
  • Removed PwdMaskFlags; use Exclusions property and/or ExcludeSymbols property
  • Used RNGCryptoServiceProvider instead of Random for random number generation
  • Updated demo application

Perfection (in design) is achieved not when there is nothing more to add, but rather when there is nothing more to take away. - Antoine de Saint-Exupéry

Version 1.1

  • Improved password generation algorithm

Version 1.0

  • Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Kevin Stewart

Web Developer

United States United States

Member

Kevin is a software development manager for a small consumer-oriented company in SoHo, NY. He is technology and platform agnostic having worked on PCs, Macintosh and various forms of Unix and Linux. His programming knowledge includes several languages, including C/C++, Java and C#. In the rare moments when his head is not buried in the latest tech book purchase from Amazon, Kevin enjoys spending time with his wife Donna and their dog Kirby.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralNice work PinmemberRockingDownTheHighway4:55 3 Feb '11  
QuestionBug in Generate() method Pinmembergeoffbishop@gmail.com9:48 15 Dec '08  
QuestionBug when exact password size requested Pinmembergeoffbishop@gmail.com9:09 15 Dec '08  
AnswerRe: Bug when exact password size requested PinmemberMember 23577407:34 24 Feb '09  
GeneralException thrown Pinmemberthom_ek2:49 29 May '08  
GeneralPronounceable passwords Pinmembermarschills11:29 16 Nov '07  
GeneralRe: Pronounceable passwords PinmemberKevin Stewart18:30 16 Nov '07  
GeneralOverriding the constructor PinmemberSpam Basket8:36 9 Mar '07  
GeneralHere's a VB Implementation... Pinmembercode-frog7:59 31 Jan '07  
GeneralRe: Here's a VB Implementation... PinmemberKevin Stewart14:28 7 Feb '07  
GeneralRe: Here's a VB Implementation... Pinmembercode-frog2:38 8 Feb '07  
GeneralRe: Here's a VB Implementation... Pinmembercode-frog2:40 8 Feb '07  
GeneralI threw you a 5... Pinmembercode-frog18:43 30 Oct '06  
GeneralRe: I threw you a 5... PinmemberKevin Stewart2:02 3 Nov '06  
QuestionCopyright Release? Pinmembertgiphil18:14 4 Oct '05  
AnswerRe: Copyright Release? PinmemberKevin Stewart4:42 5 Oct '05  
GeneralNice class PinmemberJan R Hansen20:46 17 May '05  
I sure hope it is OK for me to use this in an application I'm working on Smile | :)
 
I was just adding some comments to the class for the sake of documentation (NDoc rules !) and while trying to explain the behaviours of the Minimum and Maximum properties I ran into this issue:
 
Assuming that
 
private const int DefaultMinimum = 6;
private const int DefaultMaximum = 10;
 
then what if one sets minimum to 12 and then maximum to 10 ? In that case the code
---------------------------------------------------------------
public int Maximum
{
      get { return this.maxSize; }
      set    
      {
            this.maxSize = value;
            if ( this.minSize >= this.maxSize )
            {
                  this.maxSize = PasswordGenerator.DefaultMaximum;
            }
      }
}
---------------------------------------------------------------
 
will see that minSize >= maxSize (12 >= 10) and thus set maxSize to defaultMaximum which is 10. Then you have minSize=12 and maxSize=10 - what happens then ?
 
Anyway, I've added Dirk Vandenheuvel's validate method to my version of the class and thrown in some commens if anybody wants it (I had to turn off HTML tags in this post to preserve the xml comments):
 
---------------------------------------------------------------
 
using System;
using System.Security.Cryptography;
using System.Text;
 
namespace mynamespace
{
      /// <summary>
      /// The PasswordGenerator class is a general utility class for generating password
      /// based on a number of properties. Once one has set these properties, a random
      /// password that complys with these can be generated.
      /// </summary>
      /// <remarks>
      /// The PasswordGenerator class is originally created by Kevin Stewart as posted on
      /// http://www.codeproject.com/csharp/pwdgen.asp
      /// </remarks>
      /// <example>
      /// <code>
      /// // Create new password by using PasswordGenerator class
      /// PasswordGenerator generator = new PasswordGenerator();
      /// generator.ConsecutiveCharacters = true;
      /// generator.Exclusions = "`'~^|{}\\[](),.\";:&lt;&gt;/"; // Allowed special chars are: !@#$%&amp;*-_=+
      /// generator.Minimum = 8;
      /// generator.Maximum = 8;
      /// generator.RepeatCharacters = true;
      /// string randomPassword = generator.Generate();
      /// </code>
      /// </example>
      public class PasswordGenerator
      {
            private const int DefaultMinimum = 6;
            private const int DefaultMaximum = 10;
            private const int UBoundDigit      = 61;
 
            private RNGCryptoServiceProvider      rng;
            private int                  minSize;
            private int                  maxSize;
            private bool                  hasRepeating;
            private bool                  hasConsecutive;
            private bool                  hasSymbols;
            private string               exclusionSet;
            private char[] pwdCharArray = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_=+[]{}\\|;:'\",<.>/?".ToCharArray();                                                           
 
            /// <summary>
            /// Default constructor
            /// </summary>
            public PasswordGenerator()
            {
                  this.Minimum                              = DefaultMinimum;
                  this.Maximum                              = DefaultMaximum;
                  this.ConsecutiveCharacters         = false;
                  this.RepeatCharacters               = true;
                  this.ExcludeSymbols                  = false;
                  this.Exclusions                        = null;
 
                  rng = new RNGCryptoServiceProvider();
            }        
 
            /// <summary>
            /// Generates a random number between the specified bounds
            /// </summary>
            /// <param name="lBound">Lower bound</param>
            /// <param name="uBound">Upper bound</param>
            /// <returns>The generated random number</returns>
            protected int GetCryptographicRandomNumber(int lBound, int uBound)
            {  
                  // Assumes lBound >= 0 && lBound < uBound
                  // returns an int >= lBound and < uBound
                  uint urndnum;  
                  byte[] rndnum = new Byte[4];  
                  if (lBound == uBound-1)  
                  {
                        // test for degenerate case where only lBound can be returned  
                        return lBound;
                  }
 
                  uint xcludeRndBase = (uint.MaxValue - (uint.MaxValue%(uint)(uBound-lBound)));  
 
                  do
                  {        
                        rng.GetBytes(rndnum);        
                        urndnum = System.BitConverter.ToUInt32(rndnum,0);        
                  } while (urndnum >= xcludeRndBase);  
 
                  return (int)(urndnum % (uBound-lBound)) + lBound;
            }
 
            /// <summary>
            /// Generates a random character from the specified valid range of characters
            /// based on a generated random number between the character arrays bounds
            /// </summary>
            /// <returns>A random character within the allowed range</returns>
            protected char GetRandomCharacter()
            {                 
                  int upperBound = pwdCharArray.GetUpperBound(0);
 
                  if ( true == this.ExcludeSymbols )
                  {
                        upperBound = PasswordGenerator.UBoundDigit;
                  }
 
                  int randomCharPosition = GetCryptographicRandomNumber(pwdCharArray.GetLowerBound(0), upperBound);
 
                  char randomChar = pwdCharArray[randomCharPosition];
 
                  return randomChar;
            }
 
            /// <summary>
            /// Generates the random password based on the specified properties
            /// </summary>
            /// <returns>The random password</returns>
            public string Generate()
            {
                  // Pick random length between minimum and maximum  
                  int pwdLength = GetCryptographicRandomNumber(this.Minimum, this.Maximum);
 
                  StringBuilder pwdBuffer = new StringBuilder();
                  pwdBuffer.Capacity = this.Maximum;
 
                  // Generate random characters
                  char lastCharacter, nextCharacter;
 
                  // Initial dummy character flag
                  lastCharacter = nextCharacter = '\n';
 
                  for ( int i = 0; i < pwdLength; i++ )
                  {
                        nextCharacter = GetRandomCharacter();
 
                        if ( false == this.ConsecutiveCharacters )
                        {
                              while ( lastCharacter == nextCharacter )
                              {
                                    nextCharacter = GetRandomCharacter();
                              }
                        }
 
                        if ( false == this.RepeatCharacters )
                        {
                              string temp = pwdBuffer.ToString();
                              int duplicateIndex = temp.IndexOf(nextCharacter);
                              while ( -1 != duplicateIndex )
                              {
                                    nextCharacter = GetRandomCharacter();
                                    duplicateIndex = temp.IndexOf(nextCharacter);
                              }
                        }
 
                        if ( ( null != this.Exclusions ) )
                        {
                              while ( -1 != this.Exclusions.IndexOf(nextCharacter) )
                              {
                                    nextCharacter = GetRandomCharacter();
                              }
                        }
 
                        pwdBuffer.Append(nextCharacter);
                        lastCharacter = nextCharacter;
                  }
 
                  if ( null != pwdBuffer )
                  {
                        return pwdBuffer.ToString();
                  }
                  else
                  {
                        return String.Empty;
                  }  
            }
 
            /// <summary>
            /// The class will by default generate passwords with the characters
            /// a-z, A-Z, 0-9 and these special characters:
            /// `~!@#$%^&amp;*()-_=+[]{}\\|;:'\",&lt;.&gt;/?
            /// By specifying an Exclusions string, one can exclude some of these
            /// special characters
            /// </summary>
            public string Exclusions
            {
                  get { return this.exclusionSet;   }
                  set { this.exclusionSet = value; }
            }
 
            /// <summary>
            /// Defines the minimum length of the generated password
            /// </summary>
            /// <remarks>
            /// Note that the absolute minimum size is 6. Setting a value smaller than
            /// 6 will have no effect
            /// </remarks>
            public int Minimum
            {
                  get { return this.minSize; }
                  set
                  {
                        this.minSize = value;
                        if ( PasswordGenerator.DefaultMinimum > this.minSize )
                        {
                              this.minSize = PasswordGenerator.DefaultMinimum;
                        }
                  }
            }
 
            /// <summary>
            /// Defines the maximum length of the generated password
            /// </summary>
            /// <remarks>
            /// Note that if the maximum size is set to something   smaller than
            /// <see cref="Minimum"/>, the maximum will be set to 10
            /// </remarks>
            public int Maximum
            {
                  get { return this.maxSize; }
                  set
                  {
                        this.maxSize = value;
                        if ( this.minSize >= this.maxSize )
                        {
                              this.maxSize = PasswordGenerator.DefaultMaximum;
                        }
                  }
            }
 
            /// <summary>
            /// Defines whether the generated password may contain any special characters
            /// at all, or just letters and numbers
            /// </summary>
            public bool ExcludeSymbols
            {
                  get { return this.hasSymbols; }
                  set { this.hasSymbols = value;}
            }
 
            /// <summary>
            /// Defines whether the generated password may contain the same character
            /// more than once
            /// </summary>
            public bool RepeatCharacters
            {
                  get { return this.hasRepeating; }
                  set { this.hasRepeating = value;}
            }
 
            /// <summary>
            /// Defines whether the generated password may contain the same character
            /// repeated after itself
            /// </summary>
            public bool ConsecutiveCharacters
            {
                  get { return this.hasConsecutive; }
                  set { this.hasConsecutive = value;}
            }
     
            /// <summary>
            /// Validates a password against the defined properties
            /// </summary>
            /// <remarks>
            /// The Validate(...) method is originally posted by Dirk Vandenheuvel as a
            /// comment to the original article.
            /// </remarks>
            /// <param name="password">The password to validate</param>
            /// <returns>True if the password is valid, otherwise false</returns>
            public bool Validate(string password)
            {
                  int iCount = 0;
 
                  if (password.Length < minSize || password.Length > maxSize)
                        return(false);
 
                  // check for Consecutive characters
                  if (!hasConsecutive) // cannot have consecutive characters
                  {
                        for (iCount = 0; iCount < password.Length-1; iCount++)
                        {
                              if (password[iCount] == password[iCount+1])
                                    return(false);
                        }
                  }
 
                  if (!hasRepeating) // cannot have repeating characters
                  {
                        for (iCount = 0; iCount < password.Length; iCount++)
                        {
                              int index = password.IndexOf(password[iCount]);
                              while (index != -1)
                              {
                                    if (index != iCount)
                                          return(false);
                                    index = password.IndexOf(password[iCount]);
                              }
                        }
                  }
 
                  if (Exclusions != null) // cannot have characters from exclusion string
                  {
                        for (iCount = 0; iCount < password.Length; iCount++)
                        {
                              if (Exclusions.IndexOf(password[iCount]) != -1)
                                    return(false);
                        }
                  }
 
                  if (ExcludeSymbols) // cannot contain 'symbols'
                  {
                        for (iCount = UBoundDigit; iCount < pwdCharArray.GetUpperBound(0); iCount++)
                        {
                              if (password.IndexOf(pwdCharArray[iCount]) != -1)
                                    return(false);
                        }
                  }
 
                  return(true);
            }
      }
}
---------------------------------------------------------------
 
<span style="font-family:verdana; font-size:10px">Do you know why it's important to make fast decisions? Because you give yourself more time to correct your mistakes, when you find out that you made the wrong one. </span><span style="font-family:verdana; font-size:10px"><i>Chris Meech on deciding whether to go to his daughters graduation or a Neil Young concert</i></span>
GeneralRe: Nice class PinmemberDanny Crowell12:11 27 Jun '05  
GeneralpwdCharArray Pinmemberaztracker18:41 21 Mar '05  
GeneralInfinite Loop using RepeatCharacters as false Pinmemberfrihani11:34 7 Feb '05  
GeneralAlternative Pinmembersmallguy8:04 16 Jun '04  
GeneralRe: Alternative PinmemberKevin Stewart8:30 16 Jun '04  
GeneralRe: Alternative Pinmembersmallguy11:01 16 Jun '04  
GeneralRe: Alternative Pinmembertomstrummer8:05 24 Aug '05  
GeneralJust a quick validate for those who need it PinmemberDirk Vandenheuvel4:20 18 Feb '04  

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

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120528.1 | Last Updated 18 Aug 2003
Article Copyright 2002 by Kevin Stewart
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid