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: }