Sometimes it is necessary to generate a password. This might be done to create a secure account on a machine, or to reset a user’s password via email (although a one-time use security token is a much better answer). Another possible use is to generate passwords for your own use on a website. There are lots of ways to achieve this, but the method below would be my approach…
/// <summary> /// Creates a pseudo-random password containing the number of character classes /// defined by complexity, where 2 = alpha, 3 = alpha+num, 4 = alpha+num+special. /// </summary> public static string GeneratePassword(int length, int complexity) { System.Security.Cryptography.RNGCryptoServiceProvider csp = new System.Security.Cryptography.RNGCryptoServiceProvider(); // Define the possible character classes where complexity defines the number // of classes to include in the final output. char[][] classes = { @"abcdefghijklmnopqrstuvwxyz".ToCharArray(), @"ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray(), @"0123456789".ToCharArray(), @" !""#$%&'()*+,./:;<>?@[\]^_{|}~".ToCharArray(), }; complexity = Math.Max(1, Math.Min(classes.Length, complexity)); if(length < complexity) throw new ArgumentOutOfRangeException("length"); // Since we are taking a random number 0-255 and modulo that by the number of // characters, characters that appear earilier in this array will recieve a // heavier weight. To counter this we will then reorder the array randomly. // This should prevent any specific character class from recieving a priority // based on it's order. char[] allchars = classes.Take(complexity).SelectMany(c => c).ToArray(); byte[] bytes = new byte[allchars.Length]; csp.GetBytes(bytes); for (int i = 0; i < allchars.Length; i++) { char tmp = allchars[i]; allchars[i] = allchars[bytes[i]%allchars.Length]; allchars[bytes[i]%allchars.Length] = tmp; } // Create the random values to select the characters Array.Resize(ref bytes, length); char[] result = new char[length]; while(true) { csp.GetBytes(bytes); // Obtain the character of the class for each random byte for (int i = 0; i < length; i++) result[i] = allchars[bytes[i]%allchars.Length]; // Verify that it does not start or end with whitespace if (Char.IsWhiteSpace(result[0]) || Char.IsWhiteSpace(result[(length - 1) % length])) continue; string testResult = new string(result); // Verify that all character classes are represented if (0 != classes.Take(complexity).Count(c => testResult.IndexOfAny(c) < 0)) continue; return testResult; } }
Essentially this method starts by creating a randomly ordered set of characters to choose from. The reason this is randomly ordered is that we will offset into this array by taking a random number from 0-255 modulo the length of the array. Because of the modulo operation this can weight the first set of characters higher so we randomize it.
Once we have a randomly ordered set of characters to choose from we use the RNGCryptoServiceProvider.GetBytes to populate a byte array with random values. Each random byte will be used to select a character in the output array.
Lastly we make a few assertions about the result. Since a space ‘ ‘ can be a viable password character we make sure that the password neither starts with or ends with whitespace. Having whitespace at the beginning or end would not only be confusing, but some input forms will automatically remove the trailing whitespace. The second thing we verify is that each requested character class is represented by at least one character. This ensures that an alpha-numeric password will contain at least one number and one letter.
This brings us to the ‘how long of a password to use’ kind of question. The following is a chart to display how many password characters are required to produce an equivelent key in bit-length:
case insensitive (26 chars) |
case sensitive (52 chars) |
alpha + numeric (62 chars) |
alpha + numeric + special (92 chars) |
|
*56-bit | 12 | 10 | 9 | 7 |
*64-bit | 14 | 12 | 11 | 10 |
128-bit | 28 | 23 | 22 | 20 |
256-bit | 55 | 45 | 43 | 40 |
*56-bit keys are capable of being brute forced in just a few hours on current specialized hardware. Distributed systems are also capable of cracking 56-bit encryption. 128-bit keys are considered a minimum key space for today’s cryptographic algorithms.
The table is actually somewhat surprising to me in how little difference the added numeric and special characters make. To make a password as secure as the AES/128-bit algorithm you need 23 upper and lower case characters. Saving one keystroke by adding numbers to the mix just doesn’t seem all that worth while to me. When you look at most people using just 8-12 characters for what they consider a ‘good’ password it almost makes you laugh.
It seems to me that most password complexity rules requiring special characters and numbers make little sense. I guess it prevents dictionary attacks to a degree, but clearly the length of the password is much more important.