Came across a useful code/password generator example for strong passwords at this site:
I needed a random token generator for unit tests I wrote, so I extended it to do the following:
- Generate Alphabetical Code
- Generate Alphabetical Lower Case Code
- Generate Alphabetical Upper Case Code
- Generate Numerical Code
The code can be easily extended to include other combinations and variations.
1: #region Usings
2: using System;
3: using System.Collections.Generic;
4: using System.Linq;
5: using System.Security.Cryptography;
6: #endregion Usings
7: 8: namespace AimKai.Core.Util
9: { 10: /// <summary>
11: /// Random code generator
12: /// </summary>
13: public class RandomCodeGenerator
14: { 15: #region Constants
16: 17: /// <summary>
18: /// Default minimum code length
19: /// </summary>
20: private const int DefaultMinCodeLength = 8;
21: 22: /// <summary>
23: /// Default maximum code length
24: /// </summary>
25: private const int DefaultMaxCodeLength = 10;
26: 27: /// <summary>
28: /// Character set for lower case letters
29: /// </summary>
30: private const string CodeCharsLcase = "abcdefghijklmnopqrstuvwxyz";
31: 32: /// <summary>
33: /// Character set for upper case letters
34: /// </summary>
35: private const string CodeCharsUcase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
36: 37: /// <summary>
38: /// Character set for numerals
39: /// </summary>
40: private const string CodeNumerals = "0123456789";
41: 42: /// <summary>
43: /// Character set for specials
44: /// </summary>
45: private const string CodeCharsSpecial = "*$-+?_&=!%{}/";
46: 47: /// <summary>
48: /// Character set for non ambiguous lower cased letters
49: /// </summary>
50: private const string CodeCharsLcaseNonambig = "abcdefgijkmnopqrstwxyz";
51: 52: /// <summary>
53: /// Character set for non ambiguous upper cased letters
54: /// </summary>
55: private const string CodeCharsUcaseNonambig = "ABCDEFGHJKLMNPQRSTWXYZ";
56: 57: /// <summary>
58: /// Character set for non ambiguous letters
59: /// </summary>
60: private const string CodeNumeralsNonambig = "23456789";
61: 62: #endregion Constants
63: 64: #region Public Methods
65: 66: /// <summary>
67: /// Generates an alphanumeric random code between 8 and 10 characters
68: /// </summary>
69: /// <returns>Random code</returns>
70: public static string Generate ()
71: { 72: return Generate(DefaultMinCodeLength, DefaultMaxCodeLength, false, false, false, true, false, false);
73: } 74: 75: /// <summary>
76: /// Generates an alphanumeric random with passed minimum length and a maximum of 10 characters
77: /// </summary>
78: /// <param name="minLength">Minimum length</param>
79: /// <returns>Random Code</returns>
80: public static string Generate(int minLength)
81: { 82: CheckParameters(minLength); 83: return Generate(minLength, minLength, false, false, false, true, false, false);
84: } 85: 86: /// <summary>
87: /// Generates an alphanumeric random with passed minimum and maximum lengths
88: /// </summary>
89: /// <param name="minLength">Minimum length</param>
90: /// <param name="maxLength">Maximum length</param>
91: /// <returns>Random Code</returns>
92: public static string Generate(int minLength, int maxLength)
93: { 94: CheckParameters(minLength, maxLength); 95: return Generate(minLength, maxLength, false, false, false, true, false, false);
96: } 97: 98: /// <summary>
99: /// Generate alphabetical code
100: /// </summary>
101: /// <returns>Random code</returns>
102: public static string GenerateAlphaOnly()
103: { 104: return Generate(DefaultMinCodeLength, DefaultMaxCodeLength, true, false, false, true, false, false);
105: } 106: 107: /// <summary>
108: /// Generate alphabetical code with passed length
109: /// </summary>
110: /// <param name="minLength">Minimum length</param>
111: /// <returns>Code</returns>
112: public static string GenerateAlphaOnly(int minLength)
113: { 114: CheckParameters(minLength); 115: return Generate(minLength, minLength, true, false, false, true, false, false);
116: } 117: 118: /// <summary>
119: /// Generate alphabetical code with passed length
120: /// </summary>
121: /// <param name="minLength">Minimum length</param>
122: /// <param name="maxLength">Maximum length</param>
123: /// <returns>Code</returns>
124: public static string GenerateAlphaOnly(int minLength, int maxLength)
125: { 126: CheckParameters(minLength, maxLength); 127: return Generate(minLength, maxLength, true, false, false, true, false, false);
128: } 129: 130: /// <summary>
131: /// Generate lower case alphabetical code
132: /// </summary>
133: /// <returns>Random code</returns>
134: public static string GenerateLowerCaseAlphaOnly()
135: { 136: return Generate(DefaultMinCodeLength, DefaultMaxCodeLength, true, false, false, true, true, false);
137: } 138: 139: /// <summary>
140: /// Generate lower case alphabetical code with passed minimum length
141: /// </summary>
142: /// <param name="minLength">Minimum length</param>
143: /// <returns>Code</returns>
144: public static string GenerateLowerCaseAlphaOnly(int minLength)
145: { 146: CheckParameters(minLength); 147: return Generate(minLength, minLength, true, false, false, true, true, false);
148: } 149: 150: /// <summary>
151: /// Generate lower case alphabetical code with passed minimum and maximum length
152: /// </summary>
153: /// <param name="minLength">Minimum length</param>
154: /// <param name="maxLength">Maximum length</param>
155: /// <returns>Code</returns>
156: public static string GenerateLowerCaseAlphaOnly(int minLength, int maxLength)
157: { 158: CheckParameters(minLength, maxLength); 159: return Generate(minLength, maxLength, true, false, false, true, true, false);
160: } 161: 162: /// <summary>
163: /// Generate upper case alphabetical code
164: /// </summary>
165: /// <returns>Random code</returns>
166: public static string GenerateUpperCaseAlphaOnly()
167: { 168: return Generate(DefaultMinCodeLength, DefaultMaxCodeLength, true, false, false, true, false, true);
169: } 170: 171: /// <summary>
172: /// Generate lower case alphabetical code with passed minimum length
173: /// </summary>
174: /// <param name="minLength">Minimum length</param>
175: /// <returns>Code</returns>
176: public static string GenerateUpperCaseAlphaOnly(int minLength)
177: { 178: CheckParameters(minLength); 179: return Generate(minLength, minLength, true, false, false, true, false, true);
180: } 181: 182: /// <summary>
183: /// Generate lower case alphabetical code with passed minimum and maximum length
184: /// </summary>
185: /// <param name="minLength">Minimum length</param>
186: /// <param name="maxLength">Maximum length</param>
187: /// <returns>Code</returns>
188: public static string GenerateUpperCaseAlphaOnly(int minLength, int maxLength)
189: { 190: CheckParameters(minLength, maxLength); 191: return Generate(minLength, maxLength, true, false, false, true, false, true);
192: } 193: 194: /// <summary>
195: /// Generate numerical code
196: /// </summary>
197: /// <returns>Random code</returns>
198: public static string GenerateNumericsOnly()
199: { 200: return Generate(DefaultMinCodeLength, DefaultMaxCodeLength, false, true, false, true,
201: false, false);
202: } 203: 204: /// <summary>
205: /// Generate numerical code with minimum length
206: /// </summary>
207: /// <param name="minLength">Minimum length</param>
208: /// <returns>Random code</returns>
209: public static string GenerateNumericsOnly(int minLength)
210: { 211: CheckParameters(minLength); 212: return Generate(minLength, minLength, false, true, false, true,
213: false, false);
214: } 215: 216: /// <summary>
217: /// Generate numerical code with maximum length
218: /// </summary>
219: /// <param name="minLength">Minimum length</param>
220: /// <param name="maxLength">Maximum length</param>
221: /// <returns>Random code</returns>
222: public static string GenerateNumericsOnly(int minLength, int maxLength)
223: { 224: CheckParameters(minLength, maxLength); 225: return Generate(minLength, maxLength, false, true, false, true,
226: false, false);
227: } 228: 229: /// <summary>
230: /// Generate strong password
231: /// </summary>
232: /// <returns>Strong password</returns>
233: public static string GenerateStrongPassword()
234: { 235: return Generate(DefaultMinCodeLength, DefaultMaxCodeLength, false, false, true, false,
236: false, false);
237: } 238: 239: #endregion Public Methods
240: 241: /// <summary>
242: /// Generate code based on passed parameters
243: /// </summary>
244: /// <param name="minLength">Minimum length</param>
245: /// <param name="maxLength">Maximum length</param>
246: /// <param name="onlyLetters">Only letters</param>
247: /// <param name="onlyNumbers">Only numbers</param>
248: /// <param name="isStrong">Is strong password</param>
249: /// <param name="useAmbiguous">Use ambigous letters</param>
250: /// <param name="useLowerCaseOnly">Use lower case letters</param>
251: /// <param name="useUpperCaseOnly">Use upper case lettters</param>
252: /// <returns>Code</returns>
253: private static string Generate(int minLength, int maxLength, bool onlyLetters, bool onlyNumbers,
254: bool isStrong, bool useAmbiguous, bool useLowerCaseOnly, bool useUpperCaseOnly)
255: { 256: //Determine what character groups
257: var charGroups = DetermineCharGroups(isStrong, onlyLetters, useLowerCaseOnly, useAmbiguous, useUpperCaseOnly, onlyNumbers); 258: 259: // Use this array to track the number of unused characters in each
260: // character group.
261: var charsLeftInGroup = new int[charGroups.Length];
262: 263: // Initially, all characters in each group are not used.
264: for (var i = 0; i < charsLeftInGroup.Length; i++)
265: charsLeftInGroup[i] = charGroups[i].Length; 266: 267: // Use this array to track (iterate through) unused character groups.
268: var leftGroupsOrder = new int[charGroups.Length];
269: 270: // Initially, all character groups are not used.
271: for (var i = 0; i < leftGroupsOrder.Length; i++)
272: leftGroupsOrder[i] = i; 273: 274: // Generate random seed for random letter/numeral
275: var seed = GenerateSeed(); 276: 277: // Now, this is real randomization.
278: var random = new Random(seed);
279: 280: // Allocate appropriate memory for the code.
281: var code = minLength < maxLength ? new char[random.Next(minLength, maxLength + 1)] : new char[minLength];
282: 283: // Index of the last non-processed group.
284: var lastLeftGroupsOrderIdx = leftGroupsOrder.Length - 1; 285: 286: // Generate code characters one at a time.
287: for (var i = 0; i < code.Length; i++)
288: { 289: // If only one character group remained unprocessed, process it;
290: // otherwise, pick a random character group from the unprocessed
291: // group list. To allow a special character to appear in the
292: // first position, increment the second parameter of the Next
293: // function call by one, i.e. lastLeftGroupsOrderIdx + 1.
294: int nextLeftGroupsOrderIdx;
295: if (lastLeftGroupsOrderIdx == 0)
296: nextLeftGroupsOrderIdx = 0; 297: else
298: nextLeftGroupsOrderIdx = random.Next(0, 299: lastLeftGroupsOrderIdx); 300: 301: // Get the actual index of the character group, from which we will
302: // pick the next character.
303: var nextGroupIdx = leftGroupsOrder[nextLeftGroupsOrderIdx]; 304: 305: // Get the index of the last unprocessed characters in this group.
306: var lastCharIdx = charsLeftInGroup[nextGroupIdx] - 1; 307: 308: // If only one unprocessed character is left, pick it; otherwise,
309: // get a random character from the unused character list.
310: var nextCharIdx = lastCharIdx == 0 ? 0 : random.Next(0, lastCharIdx + 1); 311: 312: // Add this character to the code.
313: code[i] = charGroups[nextGroupIdx][nextCharIdx]; 314: 315: // If we processed the last character in this group, start over.
316: if (lastCharIdx == 0)
317: charsLeftInGroup[nextGroupIdx] = 318: charGroups[nextGroupIdx].Length; 319: // There are more unprocessed characters left.
320: else
321: { 322: // Swap processed character with the last unprocessed character
323: // so that we don't pick it until we process all characters in
324: // this group.
325: if (lastCharIdx != nextCharIdx)
326: { 327: var temp = charGroups[nextGroupIdx][lastCharIdx]; 328: charGroups[nextGroupIdx][lastCharIdx] = 329: charGroups[nextGroupIdx][nextCharIdx]; 330: charGroups[nextGroupIdx][nextCharIdx] = temp; 331: } 332: // Decrement the number of unprocessed characters in
333: // this group.
334: charsLeftInGroup[nextGroupIdx]--; 335: } 336: 337: // If we processed the last group, start all over.
338: if (lastLeftGroupsOrderIdx == 0)
339: lastLeftGroupsOrderIdx = leftGroupsOrder.Length - 1; 340: // There are more unprocessed groups left.
341: else
342: { 343: // Swap processed group with the last unprocessed group
344: // so that we don't pick it until we process all groups.
345: if (lastLeftGroupsOrderIdx != nextLeftGroupsOrderIdx)
346: { 347: var temp = leftGroupsOrder[lastLeftGroupsOrderIdx]; 348: leftGroupsOrder[lastLeftGroupsOrderIdx] = 349: leftGroupsOrder[nextLeftGroupsOrderIdx]; 350: leftGroupsOrder[nextLeftGroupsOrderIdx] = temp; 351: } 352: // Decrement the number of unprocessed groups.
353: lastLeftGroupsOrderIdx--; 354: } 355: } 356: 357: return new string(code);
358: } 359: 360: /// <summary>
361: /// Generate seed
362: /// </summary>
363: /// <returns>Seed</returns>
364: private static int GenerateSeed()
365: { 366: var randomBytes = new byte[4];
367: var rng = new RNGCryptoServiceProvider();
368: rng.GetBytes(randomBytes); 369: 370: // Convert 4 bytes into a 32-bit integer value.
371: return (randomBytes[0] & 0x7f) << 24 |
372: randomBytes[1] << 16 | 373: randomBytes[2] << 8 | 374: randomBytes[3]; 375: } 376: 377: /// <summary>
378: /// Determine character groups
379: /// </summary>
380: /// <param name="isStrong">Is strong password</param>
381: /// <param name="onlyLetters">Only letters</param>
382: /// <param name="useLowerCaseOnly">Lower case letters</param>
383: /// <param name="useAmbiguous">Ambiguous</param>
384: /// <param name="useUpperCaseOnly">Upper case letters</param>
385: /// <param name="onlyNumbers">Only numbers</param>
386: /// <returns>Character set</returns>
387: private static char[][] DetermineCharGroups(bool isStrong, bool onlyLetters, bool useLowerCaseOnly, bool useAmbiguous, bool useUpperCaseOnly, bool onlyNumbers)
388: { 389: IList<char[]> charGroups = new List<char[]>();
390: 391: if (isStrong)
392: { 393: charGroups.Add(CodeCharsLcaseNonambig.ToCharArray()); 394: charGroups.Add(CodeNumeralsNonambig.ToCharArray()); 395: charGroups.Add(CodeCharsUcaseNonambig.ToCharArray()); 396: charGroups.Add(CodeCharsSpecial.ToCharArray()); 397: } 398: else if ((onlyLetters) && (useLowerCaseOnly) && (!useAmbiguous))
399: { 400: charGroups.Add(CodeCharsLcaseNonambig.ToCharArray()); 401: } 402: else if ((onlyLetters) && (useLowerCaseOnly))
403: { 404: charGroups.Add(CodeCharsLcase.ToCharArray()); 405: } 406: else if ((onlyLetters) && (useUpperCaseOnly) && (!useAmbiguous))
407: { 408: charGroups.Add(CodeCharsUcaseNonambig.ToCharArray()); 409: } 410: else if ((onlyLetters) && (useUpperCaseOnly))
411: { 412: charGroups.Add(CodeCharsUcase.ToCharArray()); 413: } 414: else if (onlyLetters)
415: { 416: charGroups.Add(CodeCharsLcase.ToCharArray()); 417: charGroups.Add(CodeCharsUcase.ToCharArray()); 418: } 419: else if ((onlyNumbers) && (!useAmbiguous))
420: { 421: charGroups.Add(CodeNumeralsNonambig.ToCharArray()); 422: } 423: else if (onlyNumbers)
424: { 425: charGroups.Add(CodeNumerals.ToCharArray()); 426: } 427: else if (!useAmbiguous)
428: { 429: charGroups.Add(CodeCharsLcaseNonambig.ToCharArray()); 430: charGroups.Add(CodeCharsUcaseNonambig.ToCharArray()); 431: charGroups.Add(CodeNumeralsNonambig.ToCharArray()); 432: } 433: else
434: { 435: charGroups.Add(CodeCharsLcase.ToCharArray()); 436: charGroups.Add(CodeCharsUcase.ToCharArray()); 437: charGroups.Add(CodeNumerals.ToCharArray()); 438: } 439: 440: return charGroups.ToArray();
441: 442: } 443: 444: /// <summary>
445: /// Check parameters for minimum length
446: /// </summary>
447: /// <param name="minimumLength">Minimum length</param>
448: private static void CheckParameters(int minimumLength)
449: { 450: if (minimumLength <= 0)
451: throw new Exception(string.Format("Must have a positive minimum length. Length passed was '{0}'",,
452: minimumLength)); 453: } 454: 455: /// <summary>
456: /// Check parameters for maximum length
457: /// </summary>
458: /// <param name="minimumLength">Minimum length</param>
459: /// <param name="maximumLength">Maximum length</param>
460: private static void CheckParameters(int minimumLength, int maximumLength)
461: { 462: CheckParameters(minimumLength); 463: 464: if (maximumLength<= minimumLength)
465: throw new Exception(string.Format("Must have a maximum length the same or longer than the minimum length. Maximum length '{0}' and Minimum Length '{1}'.",
466: minimumLength, maximumLength)); 467: } 468: } 469: }