Skip to content

Instantly share code, notes, and snippets.

@meziantou
Created June 24, 2014 07:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save meziantou/0383d6003e9511241c0a to your computer and use it in GitHub Desktop.
Save meziantou/0383d6003e9511241c0a to your computer and use it in GitHub Desktop.
ASP.NET Identity
public class PasswordHasher : IPasswordHasher
{
public int Pbkdf2IterCount { get; set; }
public int Pbkdf2SubkeyLength { get; set; }
public int SaltSize { get; set; }
public PasswordHasher()
{
Pbkdf2IterCount = ConvertUtilities.ToInt32(ConfigurationManager.AppSettings[this.GetType().FullName + ".PBKDF2IterCount"], 50000);
Pbkdf2SubkeyLength = ConvertUtilities.ToInt32(ConfigurationManager.AppSettings[this.GetType().FullName + ".PBKDF2SubkeyLength"], 32);
SaltSize = ConvertUtilities.ToInt32(ConfigurationManager.AppSettings[this.GetType().FullName + ".SaltSize"], 16);
}
public string HashPassword(string password)
{
if (password == null)
throw new ArgumentNullException("password");
byte[] salt;
byte[] bytes;
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize, Pbkdf2IterCount))
{
salt = rfc2898DeriveBytes.Salt;
bytes = rfc2898DeriveBytes.GetBytes(Pbkdf2SubkeyLength);
}
byte[] inArray = new byte[4 + SaltSize + Pbkdf2SubkeyLength];
Buffer.BlockCopy(BitConverter.GetBytes(Pbkdf2IterCount), 0, inArray, 0, 4);
Buffer.BlockCopy(salt, 0, inArray, 4, SaltSize);
Buffer.BlockCopy(bytes, 0, inArray, 4 + SaltSize, Pbkdf2SubkeyLength);
return Convert.ToBase64String(inArray);
}
public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string password)
{
if (hashedPassword == null)
return PasswordVerificationResult.Failed;
if (password == null)
throw new ArgumentNullException("password");
byte[] numArray = Convert.FromBase64String(hashedPassword);
if (numArray.Length != (4 + SaltSize + Pbkdf2SubkeyLength))
{
return PasswordVerificationResult.Failed;
}
int iterations = BitConverter.ToInt32(numArray, 0);
byte[] salt = new byte[SaltSize];
Buffer.BlockCopy(numArray, 4, salt, 0, SaltSize);
byte[] a = new byte[Pbkdf2SubkeyLength];
Buffer.BlockCopy(numArray, 4 + SaltSize, a, 0, Pbkdf2SubkeyLength);
byte[] bytes;
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, iterations))
{
bytes = rfc2898DeriveBytes.GetBytes(Pbkdf2SubkeyLength);
}
if (ByteArraysEqual(a, bytes))
{
if (iterations != Pbkdf2IterCount)
{
return PasswordVerificationResult.SuccessRehashNeeded;
}
return PasswordVerificationResult.Success;
}
return PasswordVerificationResult.Failed;
}
private static bool ByteArraysEqual(byte[] a, byte[] b)
{
if (ReferenceEquals(a, b))
return true;
if (a == null || b == null || a.Length != b.Length)
return false;
bool flag = true;
for (int index = 0; index < a.Length; ++index)
flag = flag & (a[index] == b[index]);
return flag;
}
}
public class PasswordValidator : IIdentityValidator<string>
{
public PasswordValidator()
{
MaximumPasswordLength = int.MaxValue;
// Most used password
InvalidPasswords = new HashSet<string>();
InvalidPasswords.Add("000000");
InvalidPasswords.Add("111111");
InvalidPasswords.Add("123");
InvalidPasswords.Add("1234");
InvalidPasswords.Add("12345");
InvalidPasswords.Add("123456");
InvalidPasswords.Add("1234567");
InvalidPasswords.Add("12345678");
InvalidPasswords.Add("123456789");
InvalidPasswords.Add("1234567890");
InvalidPasswords.Add("0123456789");
InvalidPasswords.Add("password");
InvalidPasswords.Add("azerty");
InvalidPasswords.Add("qwerty");
InvalidPasswords.Add("abc123");
InvalidPasswords.Add("iloveyou");
InvalidPasswords.Add("adobe123");
InvalidPasswords.Add("123123");
InvalidPasswords.Add("admin");
InvalidPasswords.Add("letmein");
InvalidPasswords.Add("photoshop");
InvalidPasswords.Add("monkey");
InvalidPasswords.Add("shadow");
InvalidPasswords.Add("sunshine");
InvalidPasswords.Add("password1");
InvalidPasswords.Add("princess");
InvalidPasswords.Add("trustno1");
}
public int MinimumRequiredDigits { get; set; }
public int MinimumRequiredPasswordLength { get; set; }
public int MaximumPasswordLength { get; set; }
public int MinimumRequiredNonAlphanumericCharacters { get; set; }
public int MinimumRequiredLowercases { get; set; }
public int MinimumRequiredUppercases { get; set; }
public int MinimumRequiredLettersOrDigits { get; set; }
public string PasswordStrengthRegularExpression { get; set; }
protected virtual HashSet<string> InvalidPasswords { get; set; }
/// <summary>
/// Returns true if the character is a digit between '0' and '9'
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
protected virtual bool IsDigit(char c)
{
if (c < '0')
{
return false;
}
return c <= '9';
}
/// <summary>
/// Returns true if the character is between 'a' and 'z'
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
protected virtual bool IsLower(char c)
{
if (c < 'a')
{
return false;
}
return c <= 'z';
}
/// <summary>
/// Returns true if the character is between 'A' and 'Z'
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
protected virtual bool IsUpper(char c)
{
if (c < 'A')
{
return false;
}
return c <= 'Z';
}
/// <summary>
/// Ensures that the string is of the required length and meets the configured requirements
/// </summary>
/// <param name="password"></param>
/// <returns></returns>
public virtual Task<IdentityResult> ValidateAsync([NotNull]string password)
{
if (password == null)
throw new ArgumentNullException("password");
List<string> errors = new List<string>();
if (password.Length < MinimumRequiredPasswordLength)
{
errors.Add(SR.FormatPasswordTooShort(MinimumRequiredPasswordLength));
}
if (password.Length > MaximumPasswordLength)
{
errors.Add(SR.FormatPasswordTooLong(MaximumPasswordLength));
}
int digits = 0;
int lowers = 0;
int uppers = 0;
int nonAlphanum = 0;
foreach (var c in password)
{
if (IsDigit(c))
{
digits++;
}
else if (IsLower(c))
{
lowers++;
}
else if (IsUpper(c))
{
uppers++;
}
else
{
nonAlphanum++;
}
}
if (digits < MinimumRequiredDigits)
{
if (MinimumRequiredDigits >= 2)
{
errors.Add(SR.FormatPasswordRequireDigits(MinimumRequiredDigits));
}
else
{
errors.Add(SR.PasswordRequireDigit);
}
}
if (lowers < MinimumRequiredLowercases)
{
if (MinimumRequiredLowercases >= 2)
{
errors.Add(SR.FormatPasswordRequireLowers(MinimumRequiredLowercases));
}
else
{
errors.Add(SR.PasswordRequireLower);
}
}
if (uppers < MinimumRequiredUppercases)
{
if (MinimumRequiredUppercases >= 2)
{
errors.Add(SR.FormatPasswordRequireUppers(MinimumRequiredUppercases));
}
else
{
errors.Add(SR.PasswordRequireUpper);
}
}
if (nonAlphanum < MinimumRequiredNonAlphanumericCharacters)
{
if (MinimumRequiredNonAlphanumericCharacters >= 2)
{
errors.Add(SR.FormatPasswordRequireNonLettersOrDigits(MinimumRequiredNonAlphanumericCharacters));
}
else
{
errors.Add(SR.PasswordRequireNonLettersOrDigits);
}
}
if (uppers + lowers + digits < MinimumRequiredLettersOrDigits)
{
if (MinimumRequiredLettersOrDigits >= 2)
{
errors.Add(SR.FormatPasswordRequireLettersOrDigits(MinimumRequiredLettersOrDigits));
}
else
{
errors.Add(SR.PasswordRequireLettersOrDigit);
}
}
if (PasswordStrengthRegularExpression != null && Regex.IsMatch(password, PasswordStrengthRegularExpression))
{
errors.Add(SR.PasswordDoNotMatchRegex);
}
if (PasswordStrengthRegularExpression != null && Regex.IsMatch(password, PasswordStrengthRegularExpression))
{
errors.Add(SR.PasswordDoNotMatchRegex);
}
if (InvalidPasswords.Contains(password))
{
errors.Add(SR.PasswordIsInDictionary);
}
if (errors.Count == 0)
{
return Task.FromResult(IdentityResult.Success);
}
string[] strArrays = { string.Join(" ", errors) };
return Task.FromResult(IdentityResult.Failed(strArrays));
}
}
private static UserManager Create(IdentityFactoryOptions<UserManager> options, IOwinContext context)
{
UserManager userManager = new UserManager();
userManager.PasswordHasher = new Security.PasswordHasher();
userManager.PasswordValidator = new Security.PasswordValidator()
{
MinimumRequiredPasswordLength = ConfigSection.MinimumPasswordLength,
MaximumPasswordLength = ConfigSection.MaximumPasswordLength,
MinimumRequiredNonAlphanumericCharacters = 0,
MinimumRequiredDigits = 0,
MinimumRequiredLowercases = 0,
MinimumRequiredUppercases = 0
};
userManager.UserLockoutEnabledByDefault = true;
userManager.MaxFailedAccessAttemptsBeforeLockout = 15;
userManager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
userManager.EmailService = new EmailService();
userManager.UserTokenProvider = new DataProtectorTokenProvider<User, int>(options.DataProtectionProvider.Create("ResetPassword", "VerifyEmail"));
userManager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<User, int>
{
Subject = string.Format("{0} - {1}", SR.ProductName, SR.SecurityCodeSubject),
BodyFormat = SR.SecurityCode
});
return userManager;
}
public static class UserManagerExtensions
{
public static ValidateUserResult<TUser, TKey> ValidateUser<TUser, TKey>(this UserManager<TUser, TKey> userManager, string userName, string password, bool login)
where TUser : class, IUser<TKey>
where TKey : IEquatable<TKey>
{
var user = userManager.FindByName(userName);
if (user == null)
{
return new ValidateUserResult<TUser, TKey>(Resources.SR.InvalidCredential);
}
var result = userManager.CheckPassword(user, password);
if (result)
{
bool isLockedOut = userManager.IsLockedOut(user.Id);
if (userManager.SupportsUserLockout && isLockedOut)
{
DateTimeOffset lockoutEndDate = userManager.GetLockoutEndDate(user.Id);
return new ValidateUserResult<TUser, TKey>(Resources.SR.FormatUserLockedOutUntil(lockoutEndDate));
}
userManager.ResetAccessFailedCount(user.Id);
return new ValidateUserResult<TUser, TKey>(user);
}
if (login && userManager.SupportsUserLockout)
{
userManager.AccessFailed(user.Id);
}
return new ValidateUserResult<TUser, TKey>(Resources.SR.InvalidCredential);
}
}
public class ValidateUserResult<TUser, TKey>
where TUser : class, IUser<TKey>
{
public ValidateUserResult([NotNull] TUser user)
{
if (Equals(user, null)) throw new ArgumentNullException("user");
User = user;
}
public ValidateUserResult(params string[] errors)
{
if (errors == null || errors.Length == 0)
{
errors = new[] { SR.DefaultError };
}
Errors = errors;
}
public string[] Errors { get; set; }
public bool Succeeded
{
get { return (Errors == null || Errors.Length == 0) && User != null; }
}
public TUser User { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment