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)
{
uint urndnum;
byte[] rndnum = new Byte[4];
if (lBound == uBound-1)
{
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()
{
int pwdLength = GetCryptographicRandomNumber(this.Minimum,
this.Maximum);
StringBuilder pwdBuffer = new StringBuilder();
pwdBuffer.Capacity = this.Maximum;
char lastCharacter, nextCharacter;
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