Skip to content

Instantly share code, notes, and snippets.

@glyons
Created January 26, 2018 13:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save glyons/b39b56b493a2aebb19f5bd50435049d0 to your computer and use it in GitHub Desktop.
Save glyons/b39b56b493a2aebb19f5bd50435049d0 to your computer and use it in GitHub Desktop.
// : 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