Wednesday 17 September 2008

Generic Code Generator

Came across a useful code/password generator example for strong passwords at this site:

Obviex Password Example

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