|
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
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 82 (Total in Forum: 82) (Refresh) | FirstPrevNext |
|
|
 |
|
|
When Minimum and Maximum is equal then an exception is thrown. I didn't try to analyse the code to find what's wrong...
Anyway, good piece of work.
Regards, Tomek
Tomasz Muszynski
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Interesting. You might consider implementing either a Factory or Strategy pattern to make it easier to generate passwords using different algorithms (i.e. alphanumeric, passphrase, etc.) Of course, the result would no longer be "sloppycode" 
Cheers!
Kevin
"Semicolons in a programming language are like mother's milk."
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Just in case anyone wants to not have to set variables after construction, you can add in your own custom constructor using logic similar to:
public PasswordGenerator() : this(DefaultMinimum, DefaultMaximum, false, true, false, null) { }
public PasswordGenerator(bool RepeatCharacters, bool ExcludeSymbols) : this(DefaultMinimum, DefaultMaximum, false, RepeatCharacters, ExcludeSymbols, null) { }
public PasswordGenerator(int Minimum, int Maximum, bool ConsecutiveCharacters, bool RepeatCharacters, bool ExcludeSymbols, string Exclusions) { this.Minimum = Minimum; this.Maximum = Maximum; this.ConsecutiveCharacters = ConsecutiveCharacters; this.RepeatCharacters = RepeatCharacters; this.ExcludeSymbols = ExcludeSymbols; this.Exclusions = Exclusions;
rng = new RNGCryptoServiceProvider(); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Here's a VB implementation of the same. I just ran Kevin's code through a VB converter. Obviously, Kevin retains the copyright and the work. I did not lift a finger to make this work. I did use InstantVB from Tangible Software though. I'm a C# programmer but have a client that requested I work in VB I cannot think VB at all. It just doesn't come naturally so I bought InstantVB. I'm coding in C# and using Instant VB to migrate the code. So far it's been beautiful.
One word of advice. Make sure your min is less than your max or you get a div by zero error in line 36. No big deal. I didn't even debug it. Just played along with it. This also allows for a more random generation.
' This conversion is Copyright Kevin Stewart. He retains all rights. ' This code is provided as is with no warranty and certainly no ' support or obligation there-of from Kevin Stewart.
Imports Microsoft.VisualBasic Imports System Imports System.Data Imports System.Configuration Imports System.Web Imports System.Web.Security Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Web.UI.WebControls.WebParts Imports System.Web.UI.HtmlControls Imports System.Security.Cryptography Imports System.Text
Public Class PasswordGenerator Public Sub New() Me.Minimum = DefaultMinimum Me.Maximum = DefaultMaximum Me.ConsecutiveCharacters = False Me.RepeatCharacters = True Me.ExcludeSymbols = False Me.Exclusions = Nothing
rng = New RNGCryptoServiceProvider() End Sub
Protected Function GetCryptographicRandomNumber(ByVal lBound As Integer, ByVal uBound As Integer) As Integer ' Assumes lBound >= 0 && lBound < uBound ' returns an int >= lBound and < uBound Dim urndnum As UInteger Dim rndnum As Byte() = New Byte(3) {} If lBound = uBound - 1 Then ' test for degenerate case where only lBound can be returned Return lBound End If
Dim xcludeRndBase As UInteger = (UInteger.MaxValue - (UInteger.MaxValue Mod CUInt(uBound - lBound))) 'uint xcludeRndBase = (uint.MaxValue - (uint.MaxValue % (uint)(uBound - lBound)));
Do rng.GetBytes(rndnum) urndnum = System.BitConverter.ToUInt32(rndnum, 0) Loop While urndnum >= xcludeRndBase
Return CInt(Fix(urndnum Mod (uBound - lBound))) + lBound End Function
Protected Function GetRandomCharacter() As Char Dim upperBound As Integer = pwdCharArray.GetUpperBound(0)
If True = Me.ExcludeSymbols Then upperBound = PasswordGenerator.UBoundDigit End If
Dim randomCharPosition As Integer = GetCryptographicRandomNumber(pwdCharArray.GetLowerBound(0), upperBound)
Dim randomChar As Char = pwdCharArray(randomCharPosition)
Return randomChar End Function
Public Function Generate() As String ' Pick random length between minimum and maximum Dim pwdLength As Integer = GetCryptographicRandomNumber(Me.Minimum, Me.Maximum)
Dim pwdBuffer As StringBuilder = New StringBuilder() pwdBuffer.Capacity = Me.Maximum
' Generate random characters Dim lastCharacter, nextCharacter As Char
' Initial dummy character flag nextCharacter = ControlChars.Lf lastCharacter = nextCharacter
For i As Integer = 0 To pwdLength - 1 nextCharacter = GetRandomCharacter()
If False = Me.ConsecutiveCharacters Then Do While lastCharacter = nextCharacter nextCharacter = GetRandomCharacter() Loop End If
If False = Me.RepeatCharacters Then Dim temp As String = pwdBuffer.ToString() Dim duplicateIndex As Integer = temp.IndexOf(nextCharacter) Do While -1 <> duplicateIndex nextCharacter = GetRandomCharacter() duplicateIndex = temp.IndexOf(nextCharacter) Loop End If
If (Not Nothing Is Me.Exclusions) Then Do While -1 <> Me.Exclusions.IndexOf(nextCharacter) nextCharacter = GetRandomCharacter() Loop End If
pwdBuffer.Append(nextCharacter) lastCharacter = nextCharacter Next i
If Not Nothing Is pwdBuffer Then Return pwdBuffer.ToString() Else Return String.Empty End If End Function
Public Property Exclusions() As String Get Return Me.exclusionSet End Get Set(ByVal value As String) Me.exclusionSet = Value End Set End Property
Public Property Minimum() As Integer Get Return Me.minSize End Get Set(ByVal value As Integer) Me.minSize = Value If PasswordGenerator.DefaultMinimum > Me.minSize Then Me.minSize = PasswordGenerator.DefaultMinimum End If End Set End Property
Public Property Maximum() As Integer Get Return Me.maxSize End Get Set(ByVal value As Integer) Me.maxSize = Value If Me.minSize >= Me.maxSize Then Me.maxSize = PasswordGenerator.DefaultMaximum End If End Set End Property
Public Property ExcludeSymbols() As Boolean Get Return Me.hasSymbols End Get Set(ByVal value As Boolean) Me.hasSymbols = Value End Set End Property
Public Property RepeatCharacters() As Boolean Get Return Me.hasRepeating End Get Set(ByVal value As Boolean) Me.hasRepeating = Value End Set End Property
Public Property ConsecutiveCharacters() As Boolean Get Return Me.hasConsecutive End Get Set(ByVal value As Boolean) Me.hasConsecutive = Value End Set End Property
Private Const DefaultMinimum As Integer = 6 Private Const DefaultMaximum As Integer = 10 Private Const UBoundDigit As Integer = 61
Private rng As RNGCryptoServiceProvider Private minSize As Integer Private maxSize As Integer Private hasRepeating As Boolean Private hasConsecutive As Boolean Private hasSymbols As Boolean Private exclusionSet As String Private pwdCharArray As Char() = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_=+[]{}\|;:'"",<.>/?".ToCharArray() End Class
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
code-frog, I felt the same way about VB.Net. I've been a C-family developer for a long time and just couldn't warm up to VB (same thing for AppleScript, now that I develop on a Mac). Now, I tend to code in Ruby. I am glad that you were able to convert and use my code without any issue. I guess I'm curious; were you not able to call the C# implementation from VB.Net? "Language independence" used to be one of the big .NET selling points. To me, PasswordGenerator could just be treated as a class in a library that you could call from the VB code. In the end, it doesn't matter but I was curious if your client dictated that ALL code even 3rd party stuff be in VB. Thanks! 
Kevin
"Semicolons in a programming language are like mother's milk."
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm not actually sealing up this project. They need the code from top to bottom as their in-house developers will pick as much of what I do as they can. So everything needs to be in VB with source so that changes can be made. Your code could be stuck in a library but we aren't using it for passwords. We are using it to generate some random strings to guarantee a unique string when merged with some other stuff. So it's been extended a bit.
I provided the conversion for a few reasons. One I had a business reason to do it, no C# permitted. Two it seemed pretty cool to me that I could convert C# to VB so casually and that it would work. The entire conversion process for your class was under 2 seconds and I've migrated entire projects (windowless code I've written for tracking users, cookies, database wrappers) pretty much as fast. Point->Copy->Click->Paste and Voila. It just works.
Aside from that you are right, stuff it into an assembly and don't look back unless... that isn't an option and in the software world when do project managers actually make things easy?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
One other reason...
I think it's a pretty fascinating way to compare the languages. Take two pieces of code that functionally do the same thing one in C# one in VB and look at the differences. If you are on the edge of using C# or VB and want to see some comparisons there is now a short concise example burried away in your article. For me when I look at the VB conversion my stomach starts to cramp but hey... to each their own. It might help someone on the edge of indecision.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Thank you so much! I appreciate that! 
Kevin
"Semicolons in a programming language are like mother's milk."
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
The copyright status is (c) 2005 Kevin Stewart. As mentioned on the "Submit an Article" page:
If you post to CodeProject then you retain copyright of your article and code. You also give CodeProject permission to use it in a fair manner and also permit other developers to use the sourcecode associated with your articles in their own applications as long as they do not remove your copyright notices or try and take credit for your work.
I would love for you to use my code, in fact I encourage it. I haven't touched this class in awhile as I've been experimenting with Ruby lately. I do have some ideas for a new version. I may do it in Ruby first and follow up with a C# version. Glad you find this class useful!
Kevin
"Semicolons in a programming language are like mother's milk."
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I sure hope it is OK for me to use this in an application I'm working on 
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 = "`'~^|{}\\[](),.\";:<>/"; // Allowed special chars are: !@#$%&*-_=+ /// 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: /// `~!@#$%^&*()-_=+[]{}\\|;:'\",<.>/? /// 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 sam | | | | | |