/trunk/LockCrypt.Core/PasswordGenerator.cs
C# | 237 lines | 236 code | 1 blank | 0 comment | 0 complexity | 0aa824501642949e2cb88af7a3d366af MD5 | raw file
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using Org.BouncyCastle.Security;
- using Org.BouncyCastle.Crypto.Prng;
-
- namespace LockCrypt.Core {
- public class PasswordGenerator {
- public static readonly char[] BracketChars = new[] { '(', ')', '[', ']', '{', '}', '<', '>' },
- DigitChars = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
- 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' },
- 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' },
- SpecialChars = new[] { '!', '"', '£', '$', '€', '%', '^', '&', '*', '-', '_', '=', '+', '@', '#', '~', '?', '`', '/', '\\', '\'' },
- ExtendedChars = Enumerable.Range(161, (255 - 161)).Select(i => (char)i).ToArray();
-
- /// <summary>
- /// Generates passwords.
- /// </summary>
- /// <param name="specification">The password specification.</param>
- /// <param name="count">The number to generate.</param>
- /// <returns>The specified number of passwords matching the format.</returns>
- public static IEnumerable<string> GeneratePasswords(PasswordCreationArgs specification, int count) {
- IEnumerable<char> possibleChars = GetCharacterPool(specification.Brackets, specification.Digits, specification.UpperCase, specification.LowerCase, specification.ExtendedAscii, specification.SpecialCharacters, specification.ExtraCharacters);
- SecureRandom seed = new SecureRandom(new CryptoApiRandomGenerator());
- char[] possChars = possibleChars.ToArray();
- int numPossChars = possChars.Length;
- // Fisher-Yates shuffle
- int highestIndex = numPossChars-1;
- for(int i = numPossChars - 1; i > 0; i--) {
- int pos = seed.Next(0, highestIndex);
- char temp = possChars[i];
- possChars[i] = possChars[pos];
- possChars[pos] = temp;
- }
- List<string> generatedPasswords = new List<string>();
- if(specification.PasswordMask != null) {
- if(possChars.Length == 0) {
- throw new LockCryptException(I18nUtils.GetString("Errors", "NoEligibleCharactersFormat"));
- }
- string validationError = ValidateMask(specification);
- if(validationError != null) {
- throw new LockCryptException(validationError);
- }
- generatedPasswords.Add(GeneratePassword(possChars, specification.PasswordMask, specification.Length, seed));
- } else {
- if(possChars.Length == 0) {
- throw new Exception(I18nUtils.GetString("Errors", "NoEligibleCharacters"));
- }
- for(int i=0;i<count; i++) {
- generatedPasswords.Add(GeneratePassword(possChars, specification.Length, seed));
- }
- }
- return generatedPasswords;
- }
-
- /// <summary>
- /// Gets available characters for a password specification.
- /// </summary>
- /// <param name="brackets"><c>true</c> if brackets should be included, otherwise <c>false</c>.</param>
- /// <param name="digits"><c>true</c> if digits should be included, otherwise <c>false</c>.</param>
- /// <param name="uppercase"><c>true</c> if uppercase should be included, otherwise <c>false</c>.</param>
- /// <param name="lowercase"><c>true</c> if lowercase should be included, otherwise <c>false</c>.</param>
- /// <param name="extendedAscii"><c>true</c> if extended ASCII should be included, otherwise <c>false</c>.</param>
- /// <param name="specialCharacters"><c>true</c> if special characters should be included, otherwise <c>false</c>.</param>
- /// <param name="extraCharacters">Extra characters to include, or <c>null</c>.</param>
- /// <returns>A list of characters from the specified characters sets.</returns>
- private static List<char> GetCharacterPool(bool brackets, bool digits, bool uppercase, bool lowercase, bool extendedAscii, bool specialCharacters, IEnumerable<char> extraCharacters) {
- List<char> possibleChars = new List<char>();
- if(brackets) {
- possibleChars.AddRange(BracketChars);
- }
- if(digits) {
- possibleChars.AddRange(DigitChars);
- }
- if(uppercase) {
- possibleChars.AddRange(UpperCaseChars);
- }
- if(lowercase) {
- possibleChars.AddRange(LowerCaseChars);
- }
- if(specialCharacters) {
- possibleChars.AddRange(SpecialChars);
- }
- if(extendedAscii) {
- possibleChars.AddRange(ExtendedChars);
- }
- if(extraCharacters != null) {
- possibleChars.AddRange(extraCharacters);
- }
- return possibleChars.Distinct().ToList();
- }
-
- /// <summary>
- /// Generates a password.
- /// </summary>
- /// <param name="characterPool">The character pool.</param>
- /// <param name="length">The length.</param>
- /// <param name="seed">The random seed.</param>
- /// <returns>
- /// A password of the specified length from characters in the pool.
- /// </returns>
- public static string GeneratePassword(char[] characterPool, int length, SecureRandom seed) {
- int numPossible = characterPool.Count();
- if(numPossible == 0) {
- throw new ArgumentOutOfRangeException("characterPool", "No characters selected");
- } else if(length <=0) {
- throw new ArgumentOutOfRangeException("length", "Length must be greater than zero");
- }
- StringBuilder pass = new StringBuilder(length);
- int highestIndex = numPossible-1;
- for(int i = 0; i < length; i++) {
- pass.Append(characterPool[seed.Next(0, highestIndex)]);
- }
- return pass.ToString();
- }
-
- /// <summary>
- /// Validates and corrects a password mask against the characters selected.
- /// </summary>
- /// <param name="specification">The password specification.</param>
- /// <returns><c>null</c> if the password is valid, a string containing the reason a password is invalid if it's not.</returns>
- private static string ValidateMask(PasswordCreationArgs specification) {
- string validationError = null;
- if(string.IsNullOrEmpty(specification.PasswordMask)) {
- validationError = "Mask empty";
- } else {
- StringBuilder buffer = new StringBuilder(specification.PasswordMask.Replace(" ", string.Empty));
- if(!specification.Digits) {
- buffer.Replace("d", "*");
- }
- if(!specification.LowerCase) {
- buffer.Replace("c", "*");
- }
- if(!specification.UpperCase) {
- buffer.Replace("C", "*");
- }
- if(!specification.Brackets) {
- buffer.Replace("b", "*");
- }
- if(!specification.SpecialCharacters) {
- buffer.Replace("s", "*");
- }
- if(!specification.ExtendedAscii) {
- buffer.Replace("e", "*");
- }
- if(buffer.Length == 0) {
- validationError = I18nUtils.GetString("Errors", "NoEligibleCharactersFormat");
- }
- specification.PasswordMask = buffer.ToString();
- /*bool haveValidChars = specification.Digits && specification.PasswordMask.Contains('d') ||
- specification.LowerCase && specification.PasswordMask.Contains('c') ||
- specification.UpperCase && specification.PasswordMask.Contains('C') ||
- specification.Brackets && specification.PasswordMask.Contains('b') ||
- specification.SpecialCharacters && specification.PasswordMask.Contains('s') ||
- specification.ExtendedAscii && specification.PasswordMask.Contains('e');
- if(!haveValidChars) {
- validationError = I18nUtils.GetString("Errors", "NoEligibleCharactersFormat");
- }*/
- }
- return validationError;
- }
-
- /// <summary>
- /// Generates a password using a password mask.
- /// </summary>
- /// <param name="characterPool">The character pool.</param>
- /// <param name="mask">The password mask.</param>
- /// <param name="length">The length.</param>
- /// <param name="seed">The random seed.</param>
- /// <returns>
- /// A password of the specified length from characters in the pool.
- /// </returns>
- public static string GeneratePassword(char[] characterPool, string mask, int length, SecureRandom seed) {
- int numPossible = characterPool.Count();
- if(numPossible == 0) {
- throw new ArgumentOutOfRangeException("characterPool", "No characters selected");
- } else if(length <= 0) {
- throw new ArgumentOutOfRangeException("length", "Length must be greater than zero");
- }
- int highestIndex = numPossible - 1;
- StringBuilder password = new StringBuilder(length);
- int i = 0;
- while(i < length) {
- char proposed = characterPool[seed.Next(0, highestIndex)];
- if(mask.Length > i) {// can only use the mask to generate while it's long enough
- switch(mask[i]) {
- case 'd':
- if(DigitChars.Contains(proposed)) {
- password.Append(proposed);
- i++;
- }
- break;
- case 'c':
- if(LowerCaseChars.Contains(proposed)) {
- password.Append(proposed);
- i++;
- }
- break;
- case 'C':
- if(UpperCaseChars.Contains(proposed)) {
- password.Append(proposed);
- i++;
- }
- break;
- case 'b':
- if(BracketChars.Contains(proposed)) {
- password.Append(proposed);
- i++;
- }
- break;
- case 's':
- if(SpecialChars.Contains(proposed)) {
- password.Append(proposed);
- i++;
- }
- break;
- case 'e':
- if(ExtendedChars.Contains(proposed)) {
- password.Append(proposed);
- i++;
- }
- break;
- default:
- password.Append(proposed);
- i++;
- break;
- }
- } else {
- password.Append(proposed);
- i++;
- }
- }
- return password.ToString();
- }
- }
- }