Created
January 26, 2018 13:05
-
-
Save glyons/b39b56b493a2aebb19f5bd50435049d0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// : Generates random password, which complies with the strong password | |
// rules and does not contain ambiguous characters. | |
// | |
using System; | |
using System.IO; | |
using System.Text; | |
using System.Web; | |
using System.Collections; | |
using System.Security.Cryptography; | |
using System.Text.RegularExpressions; | |
/// <summary> | |
/// Password Policy Validator and Generator | |
/// | |
/// This class can generate random passwords, which do not include ambiguous | |
/// characters, such as I, l, and 1. The generated password will be made of | |
/// 7-bit ASCII symbols. Every four characters will include one lower case | |
/// character, one upper case character, one number, and one special symbol | |
/// (such as '%') in a random order. The password will always start with an | |
/// alpha-numeric character; it will not start with a special symbol (we do | |
/// this because some back-end systems do not like certain special | |
/// characters in the first position). | |
/// </summary> | |
namespace i386 | |
{ | |
public class PasswordPolicy | |
{ | |
int _minLength = 8; | |
bool _hasNumber; | |
bool _hasSymbols; | |
bool _hasUpperLower; | |
bool _hasNotExpired; | |
private StringBuilder errorMessage; | |
private static readonly RNGCryptoServiceProvider RNG = new RNGCryptoServiceProvider(); | |
// Define supported password characters divided into groups. | |
// You can add (or remove) characters to (from) these groups. | |
private static string PASSWORD_CHARS_LCASE= "abcdefgijkmnopqrstwxyz"; | |
private static string PASSWORD_CHARS_UCASE = "ABCDEFGHJKLMNPQRSTWXYZ"; | |
private static string PASSWORD_CHARS_NUM = "23456789"; | |
private static string PASSWORD_CHARS_SYM = "*$-+?&=!%"; | |
private static string ResourceFile = "Payslips"; | |
public PasswordPolicy() | |
{ | |
errorMessage = new StringBuilder(); | |
} | |
// Password Expiry | |
public bool HasNotExpired | |
{ | |
set { _hasNotExpired = value; } | |
get { return _hasNotExpired; } | |
} | |
// Mininum password length | |
public int MinLength | |
{ | |
set { _minLength = value;} | |
get { return _minLength; } | |
} | |
// Password Contains Number | |
public bool HasNumber | |
{ | |
set { _hasNumber = value; } | |
get { return _hasNumber; } | |
} | |
// Password Contains Symbols | |
public bool HasSymbols | |
{ | |
set { _hasSymbols = value; } | |
get { return _hasSymbols; } | |
} | |
// Password Contains Case Sensitive | |
public bool HasUpperLower | |
{ | |
set { _hasUpperLower = value; } | |
get { return _hasUpperLower; } | |
} | |
// Error messages | |
string _errorMsgPasswordHasExpired= "Password has expired"; | |
/// <summary> | |
/// Use to set error msg GetResourceValue("Txt:PasswordHasExpired", ResourceFile) | |
/// </summary> | |
public string ErrorMsgPasswordHasExpired | |
{ | |
set { _errorMsgPasswordHasExpired = value; } | |
get { return _errorMsgPasswordHasExpired; } | |
} | |
// Error messages | |
string _errorMsgPasswordHasRepeated = "Password has repeated characters"; | |
/// <summary> | |
/// Use to set error msg GetResourceValue("Txt:PasswordHasRepeated", ResourceFile) | |
/// </summary> | |
public string ErrorMsgPasswordHasRepeated | |
{ | |
set { _errorMsgPasswordHasRepeated = value; } | |
get { return _errorMsgPasswordHasRepeated; } | |
} | |
string _errorMsgPasswordTooShort = "Password is too short"; | |
/// <summary> | |
/// Use to set error msg GetResourceValue("Txt:PasswordTooShort", ResourceFile) | |
/// </summary> | |
public string ErrorMsgPasswordTooShort | |
{ | |
set { _errorMsgPasswordTooShort = value; } | |
get { return _errorMsgPasswordTooShort; } | |
} | |
string _errorMsgPasswordCharsOrLonger = "charactors or longer."; | |
/// <summary> | |
/// Use to set error msg GetResourceValue("Txt:PasswordCharsOrLonger", ResourceFile) | |
/// </summary> | |
public string ErrorMsgPasswordCharsOrLonger | |
{ | |
set { _errorMsgPasswordCharsOrLonger = value; } | |
get { return _errorMsgPasswordCharsOrLonger; } | |
} | |
string _errorMsgPasswordHasNoNumber = "Password has no number"; | |
/// <summary> | |
/// Use to set error msg GetResourceValue("Txt:PasswordHasNoNumber", ResourceFile) | |
/// </summary> | |
public string ErrorMsgPasswordHasNoNumber | |
{ | |
set { _errorMsgPasswordHasNoNumber = value; } | |
get { return _errorMsgPasswordHasNoNumber; } | |
} | |
string _errorMsgPasswordHasNoSymbol = "Password has no symbol"; | |
/// <summary> | |
/// Use to set error msg GetResourceValue("Txt:PasswordHasNoSymbol", ResourceFile) | |
/// </summary> | |
public string ErrorMsgPasswordHasNoSymbol | |
{ | |
set { _errorMsgPasswordHasNoSymbol = value; } | |
get { return _errorMsgPasswordHasNoSymbol; } | |
} | |
string _errorMsgPasswordHasNoCase = "Password has no upper and lower case charactors"; | |
/// <summary> | |
/// Use to set error msg - GetResourceValue("Txt:PasswordHasNoCase", ResourceFile) | |
/// </summary> | |
public string ErrorMsgPasswordHasNoCase | |
{ | |
set { _errorMsgPasswordHasNoCase = value;} | |
get { return _errorMsgPasswordHasNoCase;} | |
} | |
public bool HasRepeated(string Password, int repeated) | |
{ | |
bool hasRepeated = true; | |
Match match = null; | |
//string testString = "ABCDE AABCD"; | |
match = Regex.Match(Password, @"(\w)\1+?"); | |
if (match.Success) | |
{ | |
string matchText = match.Value; // AA | |
int matchIndex = match.Index; // 6 | |
int matchLength = match.Length; // 2 | |
if (repeated >= match.Length) | |
{ | |
hasRepeated = true; | |
errorMessage.Append(ErrorMsgPasswordHasRepeated + "\n"); // " - Password has repeated characters\n"); | |
} | |
else | |
hasRepeated = false; | |
} | |
else | |
hasRepeated = false; | |
return hasRepeated; | |
} | |
/// <summary> | |
/// Verify if the password follows the password policy | |
/// </summary> | |
/// <param name="Password"></param> | |
/// <returns></returns> | |
public bool IsValid(string Password) | |
{ | |
bool IsLengthOK = false; | |
bool withNumber = false; | |
bool withSymbol = false; | |
bool withUpperLower = false; | |
if (Password.Length < MinLength) | |
{ | |
errorMessage.Append(ErrorMsgPasswordTooShort + ", " + MinLength.ToString() + " " + ErrorMsgPasswordCharsOrLonger + "\n"); | |
IsLengthOK = false; | |
} | |
else | |
IsLengthOK = true; | |
if (HasNumber) | |
{ | |
withNumber = containsNumbers(Password); | |
if (!withNumber) errorMessage.Append(ErrorMsgPasswordHasNoNumber + "\n"); //" - Password has no number\n"); | |
} | |
else withNumber = true; | |
if (HasSymbols) | |
{ | |
withSymbol = containsPunctuation(Password); | |
if (!withSymbol) errorMessage.Append(ErrorMsgPasswordHasNoSymbol + "\n"); //" - Password has no symbol\n"); | |
} | |
else withSymbol = true; | |
if (HasUpperLower) | |
{ | |
withUpperLower = containsUpperCaseChars(Password) && containsLowerCaseChars(Password); | |
if (!withUpperLower) errorMessage.Append(ErrorMsgPasswordHasNoCase + "\n"); //" - Password has no upper and lower case charactors\n"); | |
} | |
else withUpperLower =true; | |
return withNumber && withSymbol && withUpperLower && IsLengthOK && !HasRepeated(Password, 4); | |
} | |
public static bool containsNumbers(string str) | |
{ | |
Regex pattern = new Regex(@"[\d]"); | |
return pattern.IsMatch(str); | |
} | |
public static bool containsLowerCaseChars(string str) | |
{ | |
Regex pattern = new Regex("[a-z]"); | |
return pattern.IsMatch(str); | |
} | |
public static bool containsUpperCaseChars(string str) | |
{ | |
Regex pattern = new Regex("[A-Z]"); | |
return pattern.IsMatch(str); | |
} | |
public static bool containsPunctuation(string str) | |
{ | |
// regular expression include _ as a valid char for alphanumeric.. | |
// so we need to explicity state that its considered punctuation. | |
Regex pattern = new Regex(@"[\W|_]"); | |
return pattern.IsMatch(str); | |
} | |
/// <summary> | |
/// Returns a string to tell you how good your password is. | |
/// </summary> | |
/// <param name="Password"></param> | |
/// <returns></returns> | |
public static string PasswordStrength(string Password) | |
{ | |
int charSet = 0; | |
string passStrength = ""; | |
charSet = getCharSetUsed(Password); | |
double result = Math.Log(Math.Pow(charSet, Password.Length)) / Math.Log(2); | |
if (result <= 32) | |
{ | |
passStrength = "weak;"; | |
} | |
else if (result <= 64) | |
{ | |
passStrength = "mediocre;"; | |
} | |
else if (result <= 128) | |
{ | |
passStrength = "OK;"; | |
} | |
else if (result > 128) | |
{ | |
passStrength = "great;"; | |
} | |
return "Your password is " + passStrength + | |
" it is equivalent to a " + Math.Round(result, 0) + "-bit key."; | |
} | |
private static int getCharSetUsed(string pass) | |
{ | |
int ret = 0; | |
if (containsNumbers(pass)) | |
{ | |
ret += 10; | |
} | |
if (containsLowerCaseChars(pass)) | |
{ | |
ret += 26; | |
} | |
if (containsUpperCaseChars(pass)) | |
{ | |
ret += 26; | |
} | |
if (containsPunctuation(pass)) | |
{ | |
ret += 31; | |
} | |
return ret; | |
} | |
/// <summary> | |
/// Generates a random password. | |
/// </summary> | |
/// <returns> | |
/// Randomly generated password. | |
/// </returns> | |
/// <remarks> | |
/// The length of the generated password will be determined at | |
/// random. It will be no shorter than the minimum default and | |
/// no longer than maximum default. | |
/// </remarks> | |
public string Generate() | |
{ | |
return Generate(MinLength,MinLength); | |
} | |
/// <summary> | |
/// Generates a random password of the exact length. | |
/// </summary> | |
/// <param name="length"> | |
/// Exact password length. | |
/// </param> | |
/// <returns> | |
/// Randomly generated password. | |
/// </returns> | |
public string Generate(int length) | |
{ | |
return Generate(length, length); | |
} | |
/// <summary> | |
/// Generates a random password. | |
/// </summary> | |
/// <param name="minLength"> | |
/// Minimum password length. | |
/// </param> | |
/// <param name="maxLength"> | |
/// Maximum password length. | |
/// </param> | |
/// <returns> | |
/// Randomly generated password. | |
/// </returns> | |
/// <remarks> | |
/// The length of the generated password will be determined at | |
/// random and it will fall with the range determined by the | |
/// function parameters. | |
/// </remarks> | |
public string Generate(int minLength, | |
int maxLength) | |
{ | |
string PASSWORD_CHARS_SET1 = PASSWORD_CHARS_LCASE; | |
string PASSWORD_CHARS_SET2 = PASSWORD_CHARS_LCASE; | |
string PASSWORD_CHARS_SET3 = PASSWORD_CHARS_LCASE; | |
string PASSWORD_CHARS_SET4 = PASSWORD_CHARS_LCASE; | |
if (HasNumber) | |
{ | |
PASSWORD_CHARS_SET3 = PASSWORD_CHARS_NUM; | |
} | |
if (HasSymbols) | |
{ | |
PASSWORD_CHARS_SET4 = PASSWORD_CHARS_SYM; | |
} | |
if (HasUpperLower) | |
{ | |
PASSWORD_CHARS_SET2 = PASSWORD_CHARS_UCASE; | |
} | |
// Make sure that input parameters are valid. | |
if (minLength <= 0 || maxLength <= 0 || minLength > maxLength) | |
return null; | |
// Create a local array containing supported password characters | |
// grouped by types. You can remove character groups from this | |
// array, but doing so will weaken the password strength. | |
char[][] charGroups = new char[][] | |
{ | |
PASSWORD_CHARS_SET1.ToCharArray(), | |
PASSWORD_CHARS_SET2.ToCharArray(), | |
PASSWORD_CHARS_SET3.ToCharArray(), | |
PASSWORD_CHARS_SET4.ToCharArray() | |
}; | |
// Use this array to track the number of unused characters in each | |
// character group. | |
int[] charsLeftInGroup = new int[charGroups.Length]; | |
// Initially, all characters in each group are not used. | |
for (int i = 0; i < charsLeftInGroup.Length; i++) | |
charsLeftInGroup[i] = charGroups[i].Length; | |
// Use this array to track (iterate through) unused character groups. | |
int[] leftGroupsOrder = new int[charGroups.Length]; | |
// Initially, all character groups are not used. | |
for (int i = 0; i < leftGroupsOrder.Length; i++) | |
leftGroupsOrder[i] = i; | |
// Because we cannot use the default randomizer, which is based on the | |
// current time (it will produce the same "random" number within a | |
// second), we will use a random number generator to seed the | |
// randomizer. | |
// Use a 4-byte array to fill it with random bytes and convert it then | |
// to an integer value. | |
byte[] randomBytes = new byte[4]; | |
// Generate 4 random bytes. | |
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); | |
rng.GetBytes(randomBytes); | |
// Convert 4 bytes into a 32-bit integer value. | |
int seed = (randomBytes[0] & 0x7f) << 24 | | |
randomBytes[1] << 16 | | |
randomBytes[2] << 8 | | |
randomBytes[3]; | |
// Now, this is real randomization. | |
Random random = new Random(seed); | |
// This array will hold password characters. | |
char[] password = null; | |
// Allocate appropriate memory for the password. | |
if (minLength < maxLength) | |
password = new char[random.Next(minLength, maxLength + 1)]; | |
else | |
password = new char[minLength]; | |
// Index of the next character to be added to password. | |
int nextCharIdx; | |
// Index of the next character group to be processed. | |
int nextGroupIdx; | |
// Index which will be used to track not processed character groups. | |
int nextLeftGroupsOrderIdx; | |
// Index of the last non-processed character in a group. | |
int lastCharIdx; | |
// Index of the last non-processed group. | |
int lastLeftGroupsOrderIdx = leftGroupsOrder.Length - 1; | |
// Generate password characters one at a time. | |
for (int i = 0; i < password.Length; i++) | |
{ | |
// If only one character group remained unprocessed, process it; | |
// otherwise, pick a random character group from the unprocessed | |
// group list. To allow a special character to appear in the | |
// first position, increment the second parameter of the Next | |
// function call by one, i.e. lastLeftGroupsOrderIdx + 1. | |
if (lastLeftGroupsOrderIdx == 0) | |
nextLeftGroupsOrderIdx = 0; | |
else | |
nextLeftGroupsOrderIdx = random.Next(0, | |
lastLeftGroupsOrderIdx); | |
// Get the actual index of the character group, from which we will | |
// pick the next character. | |
nextGroupIdx = leftGroupsOrder[nextLeftGroupsOrderIdx]; | |
// Get the index of the last unprocessed characters in this group. | |
lastCharIdx = charsLeftInGroup[nextGroupIdx] - 1; | |
// If only one unprocessed character is left, pick it; otherwise, | |
// get a random character from the unused character list. | |
if (lastCharIdx == 0) | |
nextCharIdx = 0; | |
else | |
nextCharIdx = random.Next(0, lastCharIdx + 1); | |
// Add this character to the password. | |
password[i] = charGroups[nextGroupIdx][nextCharIdx]; | |
// If we processed the last character in this group, start over. | |
if (lastCharIdx == 0) | |
charsLeftInGroup[nextGroupIdx] = | |
charGroups[nextGroupIdx].Length; | |
// There are more unprocessed characters left. | |
else | |
{ | |
// Swap processed character with the last unprocessed character | |
// so that we don't pick it until we process all characters in | |
// this group. | |
if (lastCharIdx != nextCharIdx) | |
{ | |
char temp = charGroups[nextGroupIdx][lastCharIdx]; | |
charGroups[nextGroupIdx][lastCharIdx] = | |
charGroups[nextGroupIdx][nextCharIdx]; | |
charGroups[nextGroupIdx][nextCharIdx] = temp; | |
} | |
// Decrement the number of unprocessed characters in | |
// this group. | |
charsLeftInGroup[nextGroupIdx]--; | |
} | |
// If we processed the last group, start all over. | |
if (lastLeftGroupsOrderIdx == 0) | |
lastLeftGroupsOrderIdx = leftGroupsOrder.Length - 1; | |
// There are more unprocessed groups left. | |
else | |
{ | |
// Swap processed group with the last unprocessed group | |
// so that we don't pick it until we process all groups. | |
if (lastLeftGroupsOrderIdx != nextLeftGroupsOrderIdx) | |
{ | |
int temp = leftGroupsOrder[lastLeftGroupsOrderIdx]; | |
leftGroupsOrder[lastLeftGroupsOrderIdx] = | |
leftGroupsOrder[nextLeftGroupsOrderIdx]; | |
leftGroupsOrder[nextLeftGroupsOrderIdx] = temp; | |
} | |
// Decrement the number of unprocessed groups. | |
lastLeftGroupsOrderIdx--; | |
} | |
} | |
// Convert password characters into a string and return the result. | |
return new string(password); | |
} | |
/// <summary> | |
/// Return the LastErrorMessages | |
/// </summary> | |
/// <returns></returns> | |
public string LastErrorMessage() | |
{ | |
return errorMessage.ToString(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment