private static Char[][] GetCharacters(PassKeyFilter filter) { if (filter == PassKeyFilter.AlphaOnly) { return new[] { PasskeyCharsLcase.ToCharArray(), PasskeyCharsUcase.ToCharArray() } } ; if (filter == PassKeyFilter.NumericOnly) { return new[] { PasskeyCharsNumericOnly.ToCharArray() } } ; if (filter == PassKeyFilter.AlphaNumeric) { return new[] { PasskeyCharsLcase.ToCharArray(), PasskeyCharsUcase.ToCharArray(), PasskeyCharsNumeric.ToCharArray() } } ; return(new[] { PasskeyCharsLcase.ToCharArray(), PasskeyCharsUcase.ToCharArray(), PasskeyCharsNumeric.ToCharArray(), PasskeyCharsSpecial.ToCharArray() }); }
/// <summary> /// Generates a random password. /// </summary> /// <param name="minLength">Minimum password length</param> /// <param name="maxLength">Maximum password length</param> /// <param name="filter">Option filtering the output of the key</param> public static string Generate(int minLength, int maxLength, PassKeyFilter filter) { // Make sure that input parameters are valid. if (minLength <= 0) { throw new ArgumentOutOfRangeException("minLength", "The minLength cannot be zero or less"); } if (minLength > maxLength) { throw new ArgumentOutOfRangeException("minLength", "The minLength cannot be greater than the maxLength"); } if (maxLength <= 0) { throw new ArgumentOutOfRangeException("maxLength", "The maxLength cannot be zero or less"); } // Create a local array containing supported passkey characters // grouped by types. You can remove character groups from this // array, but doing so will weaken the password strength. char[][] charGroups = GetCharacters(filter); int[] charsLeftInGroup = new int[charGroups.Length]; // Initially, all characters in each group are not used. for (int i = 0; i < charsLeftInGroup.Length; i++) { charsLeftInGroup[i] = charGroups[i].Length; } // Use this array to track (iterate through) unused character groups. int[] leftGroupsOrder = new int[charGroups.Length]; // Initially, all character groups are not used. for (int i = 0; i < leftGroupsOrder.Length; i++) { leftGroupsOrder[i] = i; } // This is real randomization ;) Random random = new Random(GetSeed()); // This array will hold passKey characters. // Allocate appropriate memory for the passKey. char[] passKey = minLength < maxLength ? new char[random.Next(minLength, maxLength + 1)] : new char[minLength]; int nextCharIdx; int nextGroupIdx; int nextLeftGroupsOrderIdx; int lastCharIdx; int lastLeftGroupsOrderIdx = leftGroupsOrder.Length - 1; #region Generate passKey characters one at a time for (int i = 0; i < passKey.Length; i++) { // If only one character group remained unprocessed, process it; // otherwise, pick a random character group from the unprocessed // group list. To allow a special character to appear in the // first position, increment the second parameter of the Next // function call by one, i.e. lastLeftGroupsOrderIdx + 1. nextLeftGroupsOrderIdx = (lastLeftGroupsOrderIdx == 0) ? 0 : random.Next(0, lastLeftGroupsOrderIdx); // Get the actual index of the character group, from which we will // pick the next character. nextGroupIdx = leftGroupsOrder[nextLeftGroupsOrderIdx]; // Get the index of the last unprocessed characters in this group. lastCharIdx = charsLeftInGroup[nextGroupIdx] - 1; // If only one unprocessed character is left, pick it; otherwise, // get a random character from the unused character list. nextCharIdx = (lastCharIdx == 0) ? 0 : random.Next(0, lastCharIdx + 1); // Add this character to the passKey. passKey[i] = charGroups[nextGroupIdx][nextCharIdx]; // If we processed the last character in this group, start over. if (lastCharIdx == 0) { charsLeftInGroup[nextGroupIdx] = charGroups[nextGroupIdx].Length; } // There are more unprocessed characters left. else { // Swap processed character with the last unprocessed character // so that we don't pick it until we process all characters in // this group. if (lastCharIdx != nextCharIdx) { char temp = charGroups[nextGroupIdx][lastCharIdx]; charGroups[nextGroupIdx][lastCharIdx] = charGroups[nextGroupIdx][nextCharIdx]; charGroups[nextGroupIdx][nextCharIdx] = temp; } // Decrement the number of unprocessed characters in // this group. charsLeftInGroup[nextGroupIdx]--; } // If we processed the last group, start all over. if (lastLeftGroupsOrderIdx == 0) { lastLeftGroupsOrderIdx = leftGroupsOrder.Length - 1; } // There are more unprocessed groups left. else { // Swap processed group with the last unprocessed group // so that we don't pick it until we process all groups. if (lastLeftGroupsOrderIdx != nextLeftGroupsOrderIdx) { int temp = leftGroupsOrder[lastLeftGroupsOrderIdx]; leftGroupsOrder[lastLeftGroupsOrderIdx] = leftGroupsOrder[nextLeftGroupsOrderIdx]; leftGroupsOrder[nextLeftGroupsOrderIdx] = temp; } // Decrement the number of unprocessed groups. lastLeftGroupsOrderIdx--; } } #endregion // Convert passKey characters into a string and return the result. return(new string(passKey)); }
/// <summary> /// Generates a random password of an exact length based upon the specified option /// </summary> /// <param name="length">Exact password length</param> /// <param name="filter">Option filtering the output of the key</param> public static string Generate(int length, PassKeyFilter filter) { return(Generate(length, length, filter)); }