PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/Generator/Program.cs

#
C# | 445 lines | 398 code | 22 blank | 25 comment | 184 complexity | d99d109cd7799e3d738663fab5ac52e2 MD5 | raw file
Possible License(s): Apache-2.0
  1. // Copyright 2011 Murray Grant
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. using System;
  15. using System.Collections.Generic;
  16. using System.Linq;
  17. using System.Text;
  18. using MurrayGrant.ReadablePassphrase.PhraseDescription;
  19. using MurrayGrant.ReadablePassphrase.Dictionaries;
  20. using MurrayGrant.ReadablePassphrase.Words;
  21. using MurrayGrant.ReadablePassphrase.WordTemplate;
  22. using MurrayGrant.ReadablePassphrase.Mutators;
  23. namespace MurrayGrant.ReadablePassphrase.Generator
  24. {
  25. class Program
  26. {
  27. // This is a bit of a cheats way of doing command line arguments. Please don't consider it good practice!
  28. static int count = 10;
  29. static PhraseStrength strength = PhraseStrength.Random;
  30. static string wordSeparator = " ";
  31. static bool useCustomLoader = false;
  32. static string loaderDll = "";
  33. static string loaderType = "";
  34. static string loaderArguments = "";
  35. static string customPhrasePath = "";
  36. static bool quiet = false;
  37. static bool applyStandardMutators = false;
  38. static bool applyAlternativeMutators = false;
  39. static NumericStyles numericStyle = NumericStyles.Never;
  40. static int numericCount = 2;
  41. static AllUppercaseStyles upperStyle = AllUppercaseStyles.Never;
  42. static int upperCount = 2;
  43. static IEnumerable<Clause> phraseDescription = new Clause[] { };
  44. static int maxLength = 999;
  45. static int minLength = 1;
  46. static int anyLength = 0; // Used to indicate a non-gramatic, totally random selection of word forms.
  47. static readonly int MaxAttemptsPerCount = 1000;
  48. static void Main(string[] args)
  49. {
  50. try
  51. {
  52. Console.OutputEncoding = Encoding.UTF8;
  53. if (!ParseCommandLine(args))
  54. {
  55. PrintUsage();
  56. Environment.Exit(1);
  57. }
  58. RunMain();
  59. Environment.Exit(0);
  60. }
  61. catch (Exception ex)
  62. {
  63. var originalColour = Console.ForegroundColor;
  64. Console.ForegroundColor = ConsoleColor.Red;
  65. Console.Write(ex);
  66. Console.ForegroundColor = originalColour;
  67. Environment.Exit(2);
  68. }
  69. }
  70. static void RunMain()
  71. {
  72. // Generate and print phrases.
  73. if (!quiet)
  74. {
  75. var ver = ((System.Reflection.AssemblyFileVersionAttribute)typeof(Program).Assembly.GetCustomAttributes(typeof(System.Reflection.AssemblyFileVersionAttribute), true).GetValue(0)).Version;
  76. var idx = ver.IndexOf('.', ver.IndexOf('.') + 1);
  77. Console.WriteLine("Readable Passphrase Generator {0}", ver.Substring(0, idx));
  78. }
  79. if (!quiet && anyLength > 0)
  80. Console.WriteLine("Generating {0:N0} non-grammatic phrase(s) of length '{1}'...", count, anyLength);
  81. else if (!quiet && String.IsNullOrEmpty(customPhrasePath))
  82. Console.WriteLine("Generating {0:N0} phrase(s) of strength '{1}'...", count, strength);
  83. else if (!quiet && !String.IsNullOrEmpty(customPhrasePath))
  84. Console.WriteLine("Generating {0:N0} phrase(s) based on phrase description in '{1}'...", count, System.IO.Path.GetFileName(customPhrasePath));
  85. if (!quiet && (maxLength < Int32.MaxValue || minLength > 1))
  86. Console.WriteLine("Must be between {0:N0} and {1} characters.", minLength, maxLength == Int32.MaxValue ? "∞" : maxLength.ToString("N0"));
  87. var generator = new ReadablePassphraseGenerator();
  88. // Must load dictionary before trying to generate.
  89. var dictSw = System.Diagnostics.Stopwatch.StartNew();
  90. System.Reflection.Assembly loaderAsm = null;
  91. if (useCustomLoader && !String.IsNullOrEmpty(loaderDll))
  92. loaderAsm = System.Reflection.Assembly.LoadFrom(loaderDll);
  93. Type loaderT;
  94. if (!String.IsNullOrEmpty(loaderType) && loaderAsm != null)
  95. loaderT = loaderAsm.GetTypes().FirstOrDefault(t => t.FullName.IndexOf(loaderType, StringComparison.CurrentCultureIgnoreCase) >= 0);
  96. else if (!String.IsNullOrEmpty(loaderType) && loaderAsm == null)
  97. loaderT = AppDomain.CurrentDomain.GetAssemblies()
  98. .Where(a => a.FullName.IndexOf("ReadablePassphrase", StringComparison.CurrentCultureIgnoreCase) >= 0)
  99. .SelectMany(a => a.GetTypes())
  100. .FirstOrDefault(t => t.FullName.IndexOf(loaderType, StringComparison.CurrentCultureIgnoreCase) >= 0);
  101. else if (String.IsNullOrEmpty(loaderType) && loaderAsm == null)
  102. loaderT = typeof(ExplicitXmlDictionaryLoader);
  103. else
  104. throw new ApplicationException(String.Format("Unable to find type '{0}' in {1} assembly.", loaderType, String.IsNullOrEmpty(loaderDll) ? "<default>" : loaderDll));
  105. // If the internal dictionary loader is being used and no other arguments are specified, tell it to use the default dictionary.
  106. if (loaderT == typeof(ExplicitXmlDictionaryLoader) && String.IsNullOrEmpty(loaderArguments.Trim()))
  107. loaderArguments = "useDefaultDictionary=true";
  108. // And load our dictionary!
  109. using (var loader = (IDictionaryLoader)Activator.CreateInstance(loaderT))
  110. {
  111. generator.LoadDictionary(loader, loaderArguments);
  112. }
  113. dictSw.Stop();
  114. // Summarise actions and combinations / entropy.
  115. if (!quiet)
  116. {
  117. Console.WriteLine("Dictionary contains {0:N0} words (loaded in {1:N2}ms)", generator.Dictionary.Count, dictSw.Elapsed.TotalMilliseconds);
  118. PhraseCombinations combinations;
  119. if (anyLength > 0)
  120. combinations = generator.CalculateCombinations(NonGrammaticalClause(anyLength));
  121. else if (strength != PhraseStrength.Custom)
  122. combinations = generator.CalculateCombinations(strength);
  123. else
  124. combinations = generator.CalculateCombinations(phraseDescription);
  125. Console.WriteLine("Average combinations ~{0:E3} (~{1:N2} bits)", combinations.OptionalAverage, combinations.OptionalAverageAsEntropyBits);
  126. Console.WriteLine("Total combinations {0:E3} - {1:E3} ({2:N2} - {3:N2} bits)", combinations.Shortest, combinations.Longest, combinations.ShortestAsEntropyBits, combinations.LongestAsEntropyBits);
  127. var upperTypeText = upperStyle == AllUppercaseStyles.RunOfLetters ? "run "
  128. : upperStyle == AllUppercaseStyles.WholeWord ? "word "
  129. : "";
  130. var upperTypeText2 = upperStyle == AllUppercaseStyles.RunOfLetters ? "run"
  131. : upperStyle == AllUppercaseStyles.WholeWord ? "word"
  132. : "capital";
  133. if (applyStandardMutators)
  134. Console.WriteLine("Using standard mutators (2 numbers, 2 capitals)");
  135. else if (applyAlternativeMutators)
  136. Console.WriteLine("Using alternate mutators (2 numbers, 1 whole capital word)");
  137. else if (numericStyle != 0 && upperStyle != 0)
  138. Console.WriteLine("Using upper case {2}and numeric mutators ({0:N0} {3}(s), {1:N0} number(s))", upperCount, numericCount, upperTypeText, upperTypeText2);
  139. else if (numericStyle == 0 && upperStyle != 0)
  140. Console.WriteLine("Using upper case {1}mutator only ({0:N0} {2}(s))", upperCount, upperTypeText, upperTypeText2);
  141. else if (numericStyle != 0 && upperStyle == 0)
  142. Console.WriteLine("Using numeric mutator only ({0:N0} number(s))", numericCount);
  143. else
  144. Console.WriteLine("Using no mutators");
  145. Console.WriteLine();
  146. }
  147. // Generate!
  148. var genSw = System.Diagnostics.Stopwatch.StartNew();
  149. int generated = 0;
  150. int attempts = 0;
  151. int maxAttempts = count * MaxAttemptsPerCount;
  152. var mutators = applyStandardMutators ? new IMutator[] { UppercaseMutator.Basic, NumericMutator.Basic }
  153. : applyAlternativeMutators ? new IMutator[] { UppercaseWordMutator.Basic, NumericMutator.Basic }
  154. : Enumerable.Empty<IMutator>();
  155. if (upperStyle > 0 && upperStyle <= AllUppercaseStyles.Anywhere)
  156. mutators = mutators.Concat(new IMutator[] { new UppercaseMutator() { When = (UppercaseStyles)upperStyle, NumberOfCharactersToCapitalise = upperCount } });
  157. if (upperStyle == AllUppercaseStyles.RunOfLetters)
  158. mutators = mutators.Concat(new IMutator[] { new UppercaseRunMutator() { NumberOfRuns = upperCount } });
  159. if (upperStyle == AllUppercaseStyles.WholeWord)
  160. mutators = mutators.Concat(new IMutator[] { new UppercaseWordMutator() { NumberOfWordsToCapitalise = upperCount } });
  161. if (numericStyle != 0)
  162. mutators = mutators.Concat(new IMutator[] { new NumericMutator() { When = numericStyle, NumberOfNumbersToAdd = numericCount } });
  163. while (generated < count)
  164. {
  165. // Generate phrase.
  166. // We always include spaces in the phrase because the mutators rely on whitespace.
  167. string phrase;
  168. attempts++;
  169. if (anyLength > 0)
  170. phrase = generator.Generate(NonGrammaticalClause(anyLength), " ", mutators);
  171. else if (strength == PhraseStrength.Custom)
  172. phrase = generator.Generate(phraseDescription, " ", mutators);
  173. else
  174. phrase = generator.Generate(strength, " ", mutators);
  175. // After mutators are applied, it's safe to remove white space.
  176. if (wordSeparator != " ")
  177. phrase = phrase.Replace(" ", wordSeparator);
  178. // Clamp the length.
  179. if (phrase.Length >= minLength && phrase.Length <= maxLength)
  180. {
  181. Console.WriteLine(phrase);
  182. generated++;
  183. }
  184. if (attempts >= maxAttempts)
  185. break;
  186. }
  187. genSw.Stop();
  188. // Summarise result.
  189. if (!quiet)
  190. {
  191. Console.WriteLine();
  192. Console.WriteLine("Generated {0:N0} phrase(s) in {1:N2}ms.", generated, genSw.Elapsed.TotalMilliseconds);
  193. if (attempts >= maxAttempts)
  194. Console.WriteLine("But unable to generate requested {0:N0} phrase(s) after {1:N0} attempts." + Environment.NewLine + "Perhaps try changing the minimum or maximum phrase length.", count, attempts);
  195. }
  196. }
  197. static IEnumerable<Clause> NonGrammaticalClause(int count)
  198. {
  199. for (int i = 0; i < count; i++)
  200. yield return new AnyWordClause();
  201. }
  202. static bool ParseCommandLine(string[] args)
  203. {
  204. for (int i = 0; i < args.Length; i++)
  205. {
  206. var arg = args[i].ToLower().Trim();
  207. if (arg.StartsWith("-") || arg.StartsWith("--") || arg.StartsWith("/"))
  208. arg = arg.Replace("--", "").Replace("-", "").Replace("/", "");
  209. if (arg == "c" || arg == "count")
  210. {
  211. if (!Int32.TryParse(args[i + 1].Trim(), out count))
  212. {
  213. Console.WriteLine("Unable to parse number '{0}' for 'count' option.", args[i + 1]);
  214. return false;
  215. }
  216. i++;
  217. }
  218. else if (arg == "s" || arg == "strength")
  219. {
  220. if (!Enum.GetNames(typeof(PhraseStrength)).Select(x => x.ToLower()).Contains(args[i + 1]))
  221. {
  222. Console.WriteLine("Unknown 'strength' option '{0}'.", args[i + 1]);
  223. return false;
  224. }
  225. strength = (PhraseStrength)Enum.Parse(typeof(PhraseStrength), args[i + 1], true);
  226. i++;
  227. }
  228. else if (arg == "spaces")
  229. {
  230. bool includeSpaces;
  231. if (!Boolean.TryParse(args[i + 1], out includeSpaces))
  232. {
  233. Console.WriteLine("Invalid boolean '{0}' for 'spaces' option.", args[i + 1]);
  234. return false;
  235. }
  236. else if (includeSpaces)
  237. wordSeparator = " ";
  238. else if (!includeSpaces)
  239. wordSeparator = "";
  240. i++;
  241. }
  242. else if (arg == "separator")
  243. {
  244. wordSeparator = args[i + 1];
  245. i++;
  246. }
  247. else if (arg == "d" || arg == "dict")
  248. {
  249. var customDictionaryPath = args[i + 1];
  250. loaderArguments = "file=" + customDictionaryPath;
  251. if (!System.IO.File.Exists(customDictionaryPath))
  252. {
  253. Console.WriteLine("Unable to find file '{0}' for 'dict' option.", customDictionaryPath);
  254. return false;
  255. }
  256. i++;
  257. }
  258. else if (arg == "l" || arg == "loaderdll")
  259. {
  260. loaderDll = args[i + 1];
  261. if (!System.IO.File.Exists(loaderDll))
  262. {
  263. Console.WriteLine("Unable to find file '{0}' for 'loaderdll' option.", loaderDll);
  264. return false;
  265. }
  266. i++;
  267. }
  268. else if (arg == "t" || arg == "loadertype")
  269. {
  270. loaderType = args[i + 1];
  271. i++;
  272. }
  273. else if (arg == "a" || arg == "loaderargs")
  274. {
  275. loaderArguments = args[i + 1];
  276. i++;
  277. }
  278. else if (arg == "n" || arg == "nongrammar")
  279. {
  280. if (!Int32.TryParse(args[i + 1].Trim(), out anyLength))
  281. {
  282. Console.WriteLine("Unable to parse number '{0}' for 'nongrammar' option.", args[i + 1]);
  283. return false;
  284. }
  285. i++;
  286. }
  287. else if (arg == "p" || arg == "phrase")
  288. {
  289. customPhrasePath = args[i + 1];
  290. if (!System.IO.File.Exists(customPhrasePath))
  291. {
  292. Console.WriteLine("Unable to find file '{0}' for 'phrase' option.", args[i + 1]);
  293. return false;
  294. }
  295. try
  296. {
  297. phraseDescription = ReadablePassphrase.PhraseDescription.Clause.CreateCollectionFromTextString(System.IO.File.ReadAllText(customPhrasePath));
  298. }
  299. catch (PhraseDescriptionParseException ex)
  300. {
  301. Console.WriteLine("Unable to parse file '{0}' for 'phrase' option:", args[i + 1]);
  302. Console.WriteLine(" {0}", ex.Message);
  303. if (ex.InnerException != null)
  304. Console.WriteLine(" {0}: {1}", ex.InnerException.GetType().Name, ex.InnerException.Message);
  305. }
  306. i++;
  307. }
  308. else if (arg == "min")
  309. {
  310. if (!Int32.TryParse(args[i + 1].Trim(), out minLength))
  311. {
  312. Console.WriteLine("Unable to parse number '{0}' for 'min' option.", args[i + 1]);
  313. return false;
  314. }
  315. i++;
  316. }
  317. else if (arg == "max")
  318. {
  319. if (!Int32.TryParse(args[i + 1].Trim(), out maxLength))
  320. {
  321. Console.WriteLine("Unable to parse number '{0}' for 'max' option.", args[i + 1]);
  322. return false;
  323. }
  324. i++;
  325. }
  326. else if (arg == "m" || arg == "stdMutators")
  327. {
  328. applyStandardMutators = true;
  329. }
  330. else if (arg == "m2" || arg == "altMutators")
  331. {
  332. applyAlternativeMutators = true;
  333. }
  334. else if (arg == "mutnumeric")
  335. {
  336. if (!Enum.GetNames(typeof(NumericStyles)).Select(x => x.ToLower()).Contains(args[i + 1]))
  337. {
  338. Console.WriteLine("Unknown 'mutNumeric' option '{0}'.", args[i + 1]);
  339. return false;
  340. }
  341. numericStyle = (NumericStyles)Enum.Parse(typeof(NumericStyles), args[i + 1], true);
  342. i++;
  343. }
  344. else if (arg == "mutnumericcount")
  345. {
  346. if (!Int32.TryParse(args[i + 1].Trim(), out numericCount))
  347. {
  348. Console.WriteLine("Unable to parse number '{0}' for 'mutNumericCount' option.", args[i + 1]);
  349. return false;
  350. }
  351. i++;
  352. }
  353. else if (arg == "mutupper")
  354. {
  355. if (!Enum.GetNames(typeof(AllUppercaseStyles)).Select(x => x.ToLower()).Contains(args[i + 1]))
  356. {
  357. Console.WriteLine("Unknown 'mutUpper' option '{0}'.", args[i + 1]);
  358. return false;
  359. }
  360. upperStyle = (AllUppercaseStyles)Enum.Parse(typeof(AllUppercaseStyles), args[i + 1], true);
  361. i++;
  362. }
  363. else if (arg == "mutuppercount")
  364. {
  365. if (!Int32.TryParse(args[i + 1].Trim(), out upperCount))
  366. {
  367. Console.WriteLine("Unable to parse number '{0}' for 'mutUpperCount' option.", args[i + 1]);
  368. return false;
  369. }
  370. i++;
  371. }
  372. else if (arg == "q" || arg == "quiet")
  373. {
  374. quiet = true;
  375. }
  376. else if (arg == "h" || arg == "help")
  377. {
  378. PrintUsage();
  379. Environment.Exit(0);
  380. }
  381. else
  382. {
  383. Console.WriteLine("Unknown argument '{0}'.", arg);
  384. return false;
  385. }
  386. }
  387. return true;
  388. }
  389. static void PrintUsage()
  390. {
  391. Console.WriteLine("Usage: PassphraseGenerator.exe [options]");
  392. Console.WriteLine(" -c --count nnn Generates nnn phrases (default: {0})", count);
  393. Console.WriteLine(" -s --strength xxx Selects phrase strength (default: {0})", strength);
  394. Console.WriteLine(" xxx = [normal|strong|insane][equal|required][and|speech]");
  395. Console.WriteLine(" or 'custom' or 'random[short|long|forever]'");
  396. Console.WriteLine(" --min xxx Specifies a minimum length for phrases (def: {0})", minLength);
  397. Console.WriteLine(" --max xxx Specifies a maximum length for phrases (def: {0})", maxLength);
  398. Console.WriteLine(" --spaces true|false Includes spaces between words (default: true)");
  399. Console.WriteLine(" --separator x Character(s) to separate words (default: {0})", wordSeparator);
  400. Console.WriteLine(" -n --nongrammar nn Creates non-grammatical passphrases of length nn");
  401. Console.WriteLine();
  402. Console.WriteLine(" -m --stdMutators Adds 2 numbers and 2 capitals to the passphrase");
  403. Console.WriteLine(" -m2 --altMutators Adds 2 numbers and capitalises a single word");
  404. Console.WriteLine(" --mutUpper xxx Uppercase mutator style (default: {0})", upperStyle);
  405. Console.WriteLine(" xxx = [startofword|anywhere|runofwords|wholeword]");
  406. Console.WriteLine(" --mutUpperCount nn Number of capitals to add (default: {0}", upperCount);
  407. Console.WriteLine(" --mutNumeric xxx Numeric mutator style (default: {0})", numericStyle);
  408. Console.WriteLine(" xxx = [startofword|endofword|startorendofword|endofphrase|anywhere]");
  409. Console.WriteLine(" --mutNumericCount nn Number of numbers to add (default: {0}", numericCount);
  410. Console.WriteLine();
  411. Console.WriteLine(" -l --loaderdll path Specifies a custom loader dll");
  412. Console.WriteLine(" -t --loadertype path Specifies a custom loader type");
  413. Console.WriteLine(" -a --loaderargs str Specifies arguments for custom loader");
  414. Console.WriteLine(" -d --dict str Specifies a custom dictionary file");
  415. Console.WriteLine(" -p --phrase path Specifies a custom phrase file ");
  416. Console.WriteLine(" Must use -strength custom ");
  417. Console.WriteLine();
  418. Console.WriteLine(" -q --quiet Does not display any status messages (default: {0})", quiet ? "hide" : "show");
  419. Console.WriteLine(" -h --help Displays this message ");
  420. Console.WriteLine("See {0} for more information", ReadablePassphraseGenerator.CodeplexHomepage);
  421. }
  422. }
  423. }