mo.notono.us

Thursday, April 09, 2009

C#: Generate Readable Passwords

On a recent project I found it necessary to replace the Forms Based Authentication Membership provider generated passwords with some of my own choosing, in order to make the passwords actually readable and typeable.  Call me a usability-nut, but I consider passwords like t7(N4(Jm9{]M@@ and |NxXl&^ah;z4]- (both actual ResetPassword() result examples) to be neither readable nor typeable.

So I followed Mark Fitzpatrick's approach, which essentially is to take the output of the ResetPassword method and use it as the old password input for the ChangePassword method. But now I still needed a new password, so since nothing came out of a cursory Google search, I concocted the utility method below. Note that this is neither rocket surgery nor particularly brainy science and it may not suit your purpose, but it generates a random 9-10 character password from a set of 1.5+ million, which did the trick for me:

UPDATE: The random number generator in .NET isn’t all that random, unless you force it to be – see  GetRandomIndex method at the bottom.

UPDATE 2: Silly me – you have to apply the .Next() method of the SAME random instance, otherwise you end up with very recognizable patterns.  Removed the GetRandomIndex method and added _randomInstance member variable.

 

//Create a static instance of the Random class, on which to operate
private static Random _randomInstance = new Random();

/// <summary>
/// Generates a random, but readable, password, at least 8 characters and containing one or more 
/// lowercase and uppercase letters, numbers, and symbols.
/// </summary>
/// <returns>The random password</returns>
internal static string GenerateReadablePassword()
{
  //simple short words
  string[] words = new string[40] {
"Rocket", "Ship", "Plane", "Train", "Boat",
"Space", "Earth", "Venus", "Mars", "Moon",
"Valley", "Water", "Land", "Beach", "Field",
"Puppy", "Kitten", "Tiger", "Turtle", "Bird",
"Texas", "Maine", "Kansas", "Florida", "Alaska",
"Hand", "Head", "Foot", "Wrist", "Elbow",
"Happy", "Smile", "Grin", "Humor", "Spirit",
"Soccer", "Cricket", "Tennis", "Bowling", "Yummy"
};
  //alphabets: don't include those that can be misinterpreted for another letter/number
  string alphabets = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXY";
  //numbers: exclude 0 (~= O or o), 1 (~= I, or l) and 2 (~= z)
  string numbers = "3456789";
  //common symbols: no brackets, parentheses or slashes
  string symbols = "!@#$+=&*?";

  //put the elements together in a random format, but don't stick an alphabet immediately after a word
  string[] formats = new string[18] {
"{1}{2}{3}{0}", 
"{1}{2}{0}{3}", "{2}{3}{1}{0}", "{3}{0}{2}{1}",
"{0}{2}{3}{1}", "{1}{3}{0}{2}", "{3}{1}{2}{0}",
"{0}{2}{1}{3}", "{1}{3}{2}{0}", "{2}{0}{3}{1}", "{3}{1}{0}{2}",
"{0}{3}{1}{2}", "{1}{0}{2}{3}", "{2}{1}{3}{0}", 
"{0}{3}{2}{1}", "{1}{0}{3}{2}", "{2}{1}{0}{3}", "{3}{2}{1}{0}"
};

  //combine the elements
  string password = string.Format(GetRandomString(formats), //random format
    GetRandomString(words), //0
    GetRandomStringCharacter(alphabets), //1
    GetRandomStringCharacter(numbers), //2
    GetRandomStringCharacter(symbols) //3
  );

  //post fill the password with numbers as necessary
  while (password.Length < 8)
  {
    password += GetRandomStringCharacter(numbers);
  }

  return password;
}

/// <summary>
/// Gets the random string character.
/// </summary>
/// <param name="inputString">The input string.</param>
/// <returns></returns>
private static char GetRandomStringCharacter(string inputString)
{
  return inputString[_randomInstance.Next(0, inputString.Length)];
}

/// <summary>
/// Gets the random string.
/// </summary>
/// <param name="inputStringArray">The input string array.</param>
/// <returns></returns>
private static string GetRandomString(string[] inputStringArray)
{
  return inputStringArray[_randomInstance.Next(0, inputStringArray.Length)];
}

Labels: , , , , ,

3 Comments:

Post a Comment

<< Home