PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/LockCrypt.Core/PasswordGenerator.cs

#
C# | 237 lines | 236 code | 1 blank | 0 comment | 0 complexity | 0aa824501642949e2cb88af7a3d366af MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Org.BouncyCastle.Security;
  6. using Org.BouncyCastle.Crypto.Prng;
  7. namespace LockCrypt.Core {
  8. public class PasswordGenerator {
  9. public static readonly char[] BracketChars = new[] { '(', ')', '[', ']', '{', '}', '<', '>' },
  10. DigitChars = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
  11. UpperCaseChars = new[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' },
  12. LowerCaseChars = new[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' },
  13. SpecialChars = new[] { '!', '"', '£', '$', '€', '%', '^', '&', '*', '-', '_', '=', '+', '@', '#', '~', '?', '`', '/', '\\', '\'' },
  14. ExtendedChars = Enumerable.Range(161, (255 - 161)).Select(i => (char)i).ToArray();
  15. /// <summary>
  16. /// Generates passwords.
  17. /// </summary>
  18. /// <param name="specification">The password specification.</param>
  19. /// <param name="count">The number to generate.</param>
  20. /// <returns>The specified number of passwords matching the format.</returns>
  21. public static IEnumerable<string> GeneratePasswords(PasswordCreationArgs specification, int count) {
  22. IEnumerable<char> possibleChars = GetCharacterPool(specification.Brackets, specification.Digits, specification.UpperCase, specification.LowerCase, specification.ExtendedAscii, specification.SpecialCharacters, specification.ExtraCharacters);
  23. SecureRandom seed = new SecureRandom(new CryptoApiRandomGenerator());
  24. char[] possChars = possibleChars.ToArray();
  25. int numPossChars = possChars.Length;
  26. // Fisher-Yates shuffle
  27. int highestIndex = numPossChars-1;
  28. for(int i = numPossChars - 1; i > 0; i--) {
  29. int pos = seed.Next(0, highestIndex);
  30. char temp = possChars[i];
  31. possChars[i] = possChars[pos];
  32. possChars[pos] = temp;
  33. }
  34. List<string> generatedPasswords = new List<string>();
  35. if(specification.PasswordMask != null) {
  36. if(possChars.Length == 0) {
  37. throw new LockCryptException(I18nUtils.GetString("Errors", "NoEligibleCharactersFormat"));
  38. }
  39. string validationError = ValidateMask(specification);
  40. if(validationError != null) {
  41. throw new LockCryptException(validationError);
  42. }
  43. generatedPasswords.Add(GeneratePassword(possChars, specification.PasswordMask, specification.Length, seed));
  44. } else {
  45. if(possChars.Length == 0) {
  46. throw new Exception(I18nUtils.GetString("Errors", "NoEligibleCharacters"));
  47. }
  48. for(int i=0;i<count; i++) {
  49. generatedPasswords.Add(GeneratePassword(possChars, specification.Length, seed));
  50. }
  51. }
  52. return generatedPasswords;
  53. }
  54. /// <summary>
  55. /// Gets available characters for a password specification.
  56. /// </summary>
  57. /// <param name="brackets"><c>true</c> if brackets should be included, otherwise <c>false</c>.</param>
  58. /// <param name="digits"><c>true</c> if digits should be included, otherwise <c>false</c>.</param>
  59. /// <param name="uppercase"><c>true</c> if uppercase should be included, otherwise <c>false</c>.</param>
  60. /// <param name="lowercase"><c>true</c> if lowercase should be included, otherwise <c>false</c>.</param>
  61. /// <param name="extendedAscii"><c>true</c> if extended ASCII should be included, otherwise <c>false</c>.</param>
  62. /// <param name="specialCharacters"><c>true</c> if special characters should be included, otherwise <c>false</c>.</param>
  63. /// <param name="extraCharacters">Extra characters to include, or <c>null</c>.</param>
  64. /// <returns>A list of characters from the specified characters sets.</returns>
  65. private static List<char> GetCharacterPool(bool brackets, bool digits, bool uppercase, bool lowercase, bool extendedAscii, bool specialCharacters, IEnumerable<char> extraCharacters) {
  66. List<char> possibleChars = new List<char>();
  67. if(brackets) {
  68. possibleChars.AddRange(BracketChars);
  69. }
  70. if(digits) {
  71. possibleChars.AddRange(DigitChars);
  72. }
  73. if(uppercase) {
  74. possibleChars.AddRange(UpperCaseChars);
  75. }
  76. if(lowercase) {
  77. possibleChars.AddRange(LowerCaseChars);
  78. }
  79. if(specialCharacters) {
  80. possibleChars.AddRange(SpecialChars);
  81. }
  82. if(extendedAscii) {
  83. possibleChars.AddRange(ExtendedChars);
  84. }
  85. if(extraCharacters != null) {
  86. possibleChars.AddRange(extraCharacters);
  87. }
  88. return possibleChars.Distinct().ToList();
  89. }
  90. /// <summary>
  91. /// Generates a password.
  92. /// </summary>
  93. /// <param name="characterPool">The character pool.</param>
  94. /// <param name="length">The length.</param>
  95. /// <param name="seed">The random seed.</param>
  96. /// <returns>
  97. /// A password of the specified length from characters in the pool.
  98. /// </returns>
  99. public static string GeneratePassword(char[] characterPool, int length, SecureRandom seed) {
  100. int numPossible = characterPool.Count();
  101. if(numPossible == 0) {
  102. throw new ArgumentOutOfRangeException("characterPool", "No characters selected");
  103. } else if(length <=0) {
  104. throw new ArgumentOutOfRangeException("length", "Length must be greater than zero");
  105. }
  106. StringBuilder pass = new StringBuilder(length);
  107. int highestIndex = numPossible-1;
  108. for(int i = 0; i < length; i++) {
  109. pass.Append(characterPool[seed.Next(0, highestIndex)]);
  110. }
  111. return pass.ToString();
  112. }
  113. /// <summary>
  114. /// Validates and corrects a password mask against the characters selected.
  115. /// </summary>
  116. /// <param name="specification">The password specification.</param>
  117. /// <returns><c>null</c> if the password is valid, a string containing the reason a password is invalid if it's not.</returns>
  118. private static string ValidateMask(PasswordCreationArgs specification) {
  119. string validationError = null;
  120. if(string.IsNullOrEmpty(specification.PasswordMask)) {
  121. validationError = "Mask empty";
  122. } else {
  123. StringBuilder buffer = new StringBuilder(specification.PasswordMask.Replace(" ", string.Empty));
  124. if(!specification.Digits) {
  125. buffer.Replace("d", "*");
  126. }
  127. if(!specification.LowerCase) {
  128. buffer.Replace("c", "*");
  129. }
  130. if(!specification.UpperCase) {
  131. buffer.Replace("C", "*");
  132. }
  133. if(!specification.Brackets) {
  134. buffer.Replace("b", "*");
  135. }
  136. if(!specification.SpecialCharacters) {
  137. buffer.Replace("s", "*");
  138. }
  139. if(!specification.ExtendedAscii) {
  140. buffer.Replace("e", "*");
  141. }
  142. if(buffer.Length == 0) {
  143. validationError = I18nUtils.GetString("Errors", "NoEligibleCharactersFormat");
  144. }
  145. specification.PasswordMask = buffer.ToString();
  146. /*bool haveValidChars = specification.Digits && specification.PasswordMask.Contains('d') ||
  147. specification.LowerCase && specification.PasswordMask.Contains('c') ||
  148. specification.UpperCase && specification.PasswordMask.Contains('C') ||
  149. specification.Brackets && specification.PasswordMask.Contains('b') ||
  150. specification.SpecialCharacters && specification.PasswordMask.Contains('s') ||
  151. specification.ExtendedAscii && specification.PasswordMask.Contains('e');
  152. if(!haveValidChars) {
  153. validationError = I18nUtils.GetString("Errors", "NoEligibleCharactersFormat");
  154. }*/
  155. }
  156. return validationError;
  157. }
  158. /// <summary>
  159. /// Generates a password using a password mask.
  160. /// </summary>
  161. /// <param name="characterPool">The character pool.</param>
  162. /// <param name="mask">The password mask.</param>
  163. /// <param name="length">The length.</param>
  164. /// <param name="seed">The random seed.</param>
  165. /// <returns>
  166. /// A password of the specified length from characters in the pool.
  167. /// </returns>
  168. public static string GeneratePassword(char[] characterPool, string mask, int length, SecureRandom seed) {
  169. int numPossible = characterPool.Count();
  170. if(numPossible == 0) {
  171. throw new ArgumentOutOfRangeException("characterPool", "No characters selected");
  172. } else if(length <= 0) {
  173. throw new ArgumentOutOfRangeException("length", "Length must be greater than zero");
  174. }
  175. int highestIndex = numPossible - 1;
  176. StringBuilder password = new StringBuilder(length);
  177. int i = 0;
  178. while(i < length) {
  179. char proposed = characterPool[seed.Next(0, highestIndex)];
  180. if(mask.Length > i) {// can only use the mask to generate while it's long enough
  181. switch(mask[i]) {
  182. case 'd':
  183. if(DigitChars.Contains(proposed)) {
  184. password.Append(proposed);
  185. i++;
  186. }
  187. break;
  188. case 'c':
  189. if(LowerCaseChars.Contains(proposed)) {
  190. password.Append(proposed);
  191. i++;
  192. }
  193. break;
  194. case 'C':
  195. if(UpperCaseChars.Contains(proposed)) {
  196. password.Append(proposed);
  197. i++;
  198. }
  199. break;
  200. case 'b':
  201. if(BracketChars.Contains(proposed)) {
  202. password.Append(proposed);
  203. i++;
  204. }
  205. break;
  206. case 's':
  207. if(SpecialChars.Contains(proposed)) {
  208. password.Append(proposed);
  209. i++;
  210. }
  211. break;
  212. case 'e':
  213. if(ExtendedChars.Contains(proposed)) {
  214. password.Append(proposed);
  215. i++;
  216. }
  217. break;
  218. default:
  219. password.Append(proposed);
  220. i++;
  221. break;
  222. }
  223. } else {
  224. password.Append(proposed);
  225. i++;
  226. }
  227. }
  228. return password.ToString();
  229. }
  230. }
  231. }