C# Random Password Generator
I needed to generate a random password in C# and I wanted to do it right.
I consulted a security expert and he mentioned that I should use a seed created with RNGCryptoServiceProvider so I did a search and quickly found this blog entry that had a good start.
http://eyeung003.blogspot.com/2010/09/c-random-password-generator.html
However, I needed three enhancements to this code.
- Store the password as a
SecureString
. - Make upper case and lower case characters separate options.
- Guarantee that each character option would be used.
So I added these enhancements. Here are some notes about the enhancements:
- SecureString – Since most password code requires strings instead of secure strings, even code such as a SQL connection strings, I cannot fault the original writer for leaving the password as a string. However, passwords should be stored as SecureString objects as much as possible. With my enhancements, it still flips back and forth between secure string and string, but hopefully is a SecureString as often as possible.
- I include the ability to convert the SecureString to a string, because of the issue mentions in the previous bullet point.
- I separated the character options. I also added back the characters that were listed as confusing. If someone thinks these are confusing, use a font where they are not confusing or remove them again.
- Using all options – I guarantee that each option is used. The first characters in the password are chosen, one from each option, in a random order. I still didn’t like that, I created a scramble but in order to create the scramble, I had pull the password out as a string to get each character to scramble.
- I also added some exception objects to make exceptions clear if there are any.
- I also tested this on C# (Mono) on FreeBSD and it works.
Ok, here is the new version that includes my enhancements to the original code.
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Security; using System.Security.Cryptography; namespace System.Security { public enum CharacterTypes : byte { Alpha_Lower = 1, Alpha_Upper = 2, Alpha_Upper_and_Lower = 3, Digit = 4, AlphaLowerNumeric = Digit + Alpha_Lower, // 5 (4+1) AlphaUpperNumeric = Digit + Alpha_Upper, // 6 (4+2) AlphaNumeric = Alpha_Upper_and_Lower + Digit, // 7 (4+3) Special = 8, // You could add more character types here such as Alpha_Lower + Special, but why? AlphaNumericSpecial = AlphaNumeric + Special // 15 (8+7) } public class RandomPasswordGenerator { // Define default password length. private static int DEFAULT_PASSWORD_LENGTH = 16; private static PasswordOption AlphaLC = new PasswordOption() { Characters = "abcdefghijklmnopqrstuvwxyz", Count = 0 }; private static PasswordOption AlphaUC = new PasswordOption() { Characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", Count = 0 }; private static PasswordOption Digits = new PasswordOption() { Characters = "0123456789", Count = 0 }; private static PasswordOption Specials = new PasswordOption() { Characters = "!@#$%^&*()~<>?", Count = 0 }; #region Overloads /// <summary> /// Generates a random password with the default length. /// </summary> /// <returns>Randomly generated password.</returns> public static SecureString Generate() { return Generate(DEFAULT_PASSWORD_LENGTH, CharacterTypes.AlphaNumericSpecial); } /// <summary> /// Generates a random password with the default length. /// </summary> /// <returns>Randomly generated password.</returns> public static SecureString Generate(CharacterTypes option) { return Generate(DEFAULT_PASSWORD_LENGTH, option); } /// <summary> /// Generates a random password with the specified length. /// </summary> /// <returns>Randomly generated password.</returns> public static SecureString Generate(int passwordLength) { return Generate(passwordLength, CharacterTypes.AlphaNumericSpecial); } /// <summary> /// Generates a random password. /// </summary> /// <returns>Randomly generated password.</returns> public static SecureString Generate(int passwordLength, CharacterTypes option) { return GeneratePassword(passwordLength, option); } #endregion /// <summary> /// Generates the password. /// </summary> /// <returns></returns> private static SecureString GeneratePassword(int passwordLength, CharacterTypes option) { // Password length must at lest be 1 character long if (passwordLength < 1) throw new InvalidPasswordLengthException(); // Character type must be a valid CharacterType if (option < CharacterTypes.Alpha_Lower || option > CharacterTypes.AlphaNumericSpecial) throw new InvalidPasswordCharacterTypeException(); PasswordOptions passwordOptions = GetCharacters(option); // Make sure the password is long enough. // For example CharacterTypes.AlphaNumericSpecial // requires at least 4 characters: 1 upper, 1 lower, 1 digit, 1 special if (passwordLength < passwordOptions.Count) throw new InvalidPasswordLengthException(); SecureString securePassword = new SecureString(); string passwordChars = String.Empty; foreach (PasswordOption po in passwordOptions) { passwordChars += po.Characters; } if (string.IsNullOrEmpty(passwordChars)) return null; var random = RandomSeedGenerator.GetRandom(); for (int i = 0; i < passwordLength; i++) { int index; char passwordChar; if (!passwordOptions.AllOptionsAreUsed) { PasswordOption po = passwordOptions.GetUnusedOption(); index = random.Next(po.Characters.Length); passwordChar = po.Characters[index]; } else { index = random.Next(passwordChars.Length); passwordChar = passwordChars[index]; } securePassword.AppendChar(passwordChar); } return securePassword; } private int GetOptionsUsed() { int ret = 0; foreach (CharacterTypes option in Enum.GetValues(typeof(CharacterTypes))) { } return ret; } /// <summary> /// Gets the characters selected by the option /// </summary> /// <returns></returns> private static PasswordOptions GetCharacters(CharacterTypes option) { PasswordOptions list = new PasswordOptions(); switch (option) { case CharacterTypes.Alpha_Lower: list.Add(AlphaLC); break; case CharacterTypes.Alpha_Upper: list.Add(AlphaUC); break; case CharacterTypes.Alpha_Upper_and_Lower: list.Add(AlphaLC); list.Add(AlphaUC); break; case CharacterTypes.Digit: list.Add(Digits); break; case CharacterTypes.AlphaNumeric: list.Add(AlphaLC); list.Add(AlphaUC); list.Add(Digits); break; case CharacterTypes.Special: list.Add(Specials); break; case CharacterTypes.AlphaNumericSpecial: list.Add(AlphaLC); list.Add(AlphaUC); list.Add(Digits); list.Add(Specials); break; default: break; } return list; } } public static class RandomSeedGenerator { /// <summary> /// Gets a random object with a real random seed /// </summary> /// <returns></returns> public static Random GetRandom() { // Use a 4-byte array to fill it with random bytes and convert it then // to an integer value. byte[] randomBytes = new byte[4]; // Generate 4 random bytes. RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(randomBytes); // Convert 4 bytes into a 32-bit integer value. int seed = (randomBytes[0] & 0x7f) << 24 | randomBytes[1] << 16 | randomBytes[2] << 8 | randomBytes[3]; // Now, this is real randomization. return new Random(seed); } } public class PasswordOption { public int Count { get; set; } public String Characters { get; set; } } public class PasswordOptions : List<PasswordOption> { public bool AllOptionsAreUsed { get { foreach (PasswordOption po in this) { if (po.Count < 1) return false; } return true; } } public PasswordOption GetUnusedOption() { PasswordOptions options = new PasswordOptions(); foreach (PasswordOption po in this) { if (po.Count < 1) options.Add(po); } if (options.Count < 1) return null; var random = RandomSeedGenerator.GetRandom(); int optionIndex = random.Next(options.Count); return options[optionIndex]; } } public class InvalidPasswordLengthException : ArgumentException { } public class InvalidPasswordCharacterTypeException : ArgumentException { } public static class SecureStringExtender { public static string ConvertToPlainTextString(this SecureString securePassword) { if (securePassword == null) throw new ArgumentNullException("securePassword"); IntPtr unmanagedString = IntPtr.Zero; try { unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword); return Marshal.PtrToStringUni(unmanagedString); } finally { Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString); } } public static SecureString Scramble(this SecureString securePassword) { SecureString retSS = securePassword; Random random = RandomSeedGenerator.GetRandom(); int moves = random.Next(securePassword.Length, securePassword.Length * 2); for (int i = 0; i < moves; i++) { int origIndex = random.Next(securePassword.Length); int newIndex = random.Next(securePassword.Length); char c = retSS.GetAt(origIndex); retSS.InsertAt(newIndex, c); } return retSS; } public static Char GetAt(this SecureString securePassword, int index) { if (securePassword.Length < index) throw new ArgumentException("The index parameter must not be greater than the string's length."); if (index < 0) throw new ArgumentException("The index must be 0 or greater."); return securePassword.ConvertToPlainTextString().Substring(index, 1).ToCharArray()[0]; } } } [/sourcecode] Now if you want to make a simple command line executable that uses the code above, just create a new console project and call this line: <code>Console.WriteLine(RandomPasswordGenerator.Generate().ConvertToPlainTextString());</code> I added more code to handle command line parameters. [sourcecode language="csharp"] using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Security; namespace ConsoleApplication2 { class Program { private static int PasswordLength; private static String Characters; static void Main(string[] args) { if (args.Length > 2) { ShowArgs(); return; } if (args.Length > 0) { foreach (char c in args[0]) { if (char.IsDigit(c)) { ShowArgs(); return; } } PasswordLength = Convert.ToInt32(args[0]); } if (args.Length == 2) Characters = args[1]; Console.WriteLine(RandomPasswordGenerator.Generate().ConvertToPlainTextString()); } private static void ShowArgs() { String fullExeNameAndPath = System.Reflection.Assembly.GetExecutingAssembly().Location; String ExeName = System.IO.Path.GetFileName(fullExeNameAndPath); Console.WriteLine("Usage: " + ExeName + " [int] [string]"); Console.WriteLine("[int] - The length of the password. By default it is 11."); Console.WriteLine("[string] - The characters to use for the password. By default it is alphanumeric case sensitive."); } }
Hope you find this helpful. If you find flaws, please comment!