I am working on implementing security in an ASP.NET MVC 3 application, and am using the BCrypt implementation found here to handle encryption and password verification. The user registration screen encrypts the password that the user provides just fine, and the hashed password is stored in the database. I have a problem with password verification on the login page, although I canβt understand why.
My registration controller action contains the following:
[HttpPost] [RequireHttps] public ActionResult Register(Registration registration) { // Validation logic... try { var user = new User { Username = registration.Username, Password = Password.Hash(HttpUtility.HtmlDecode(registration.Password)), EmailAddress = registration.EmailAddress, FirstName = registration.FirstName, MiddleInitial = registration.MiddleInitial, LastName = registration.LastName, DateCreated = DateTime.Now, DateModified = DateTime.Now, LastLogin = DateTime.Now }; var userId = _repository.CreateUser(user); } catch (Exception ex) { ModelState.AddModelError("User", "Error creating user, please try again."); return View(registration); } // Do some other stuff... }
This is the password. Hash:
public static string Hash(string password) { return BCrypt.HashPassword(password, BCrypt.GenerateSalt(12)); }
This is how I handle the login:
[HttpPost] [RequireHttps] public ActionResult Login(Credentials login) { // Validation logic... var authorized = _repository.CredentialsAreValid(HttpUtility.HtmlDecode(login.username), login.password); if (authorized) { // log the user in... } else { ModelState.AddModelError("AuthFail", "Authentication failed, please try again"); return View(login); } }
CredentialsAreValid terminates the BCrypt.CheckPassword call:
public bool CredentialsAreValid(string username, string password) { var user = GetUser(username); if (user == null) return false; return Password.Compare(password, user.Password); }
Password.Compare:
public static bool Compare(string password, string hash) { return BCrypt.CheckPassword(password, hash); }
And finally, this is what BCrypt.CheckPassword does:
public static bool CheckPassword(string plaintext, string hashed) { return StringComparer.Ordinal.Compare(hashed, HashPassword(plaintext, hashed)) == 0; }
So yes ... I donβt know what is happening, but what I know is that my boolean authorized in my login controller action always returns false for some reason.
I have used this same BCrypt class in at least several other projects in the past and have never experienced any problems with it. Is ASP.NET MVC 3 some kind of weird, different encoding for published data that I am missing or need to handle differently or something else? Either this, or SQL CE 4 does this (what is the data store that I am currently using)? Everything seems to be okay with my code, what can I say, but for some reason the password check does not work every time. Does anyone have any ideas?
Thanks.
UPDATE: here are the code comments included in the BCrypt class, with examples of how it is used and works.
/// <summary>BCrypt implements OpenBSD-style Blowfish password hashing /// using the scheme described in "A Future-Adaptable Password Scheme" /// by Niels Provos and David Mazieres.</summary> /// <remarks> /// <para>This password hashing system tries to thwart offline /// password cracking using a computationally-intensive hashing /// algorithm, based on Bruce Schneier Blowfish cipher. The work /// factor of the algorithm is parametized, so it can be increased as /// computers get faster.</para> /// <para>To hash a password for the first time, call the /// <c>HashPassword</c> method with a random salt, like this:</para> /// <code> /// string hashed = BCrypt.HashPassword(plainPassword, BCrypt.GenerateSalt()); /// </code> /// <para>To check whether a plaintext password matches one that has /// been hashed previously, use the <c>CheckPassword</c> method:</para> /// <code> /// if (BCrypt.CheckPassword(candidatePassword, storedHash)) { /// Console.WriteLine("It matches"); /// } else { /// Console.WriteLine("It does not match"); /// } /// </code> /// <para>The <c>GenerateSalt</c> method takes an optional parameter /// (logRounds) that determines the computational complexity of the /// hashing:</para> /// <code> /// string strongSalt = BCrypt.GenerateSalt(10); /// string strongerSalt = BCrypt.GenerateSalt(12); /// </code> /// <para> /// The amount of work increases exponentially (2**log_rounds), so /// each increment is twice as much work. The default log_rounds is /// 10, and the valid range is 4 to 31. /// </para> /// </remarks>