I'm working on implementing security in an ASP.NET MVC 3 application, and am using the BCrypt implementation found here to handle encryption and verification of passwords. The user registration screen encrypts the password the user provides just fine, and the hashed password gets saved to the database. I'm having a problem with password verification on the login page though, and I can't seem to figure out 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 Password.Hash:
public static string Hash(string password)
{
return BCrypt.HashPassword(password, BCrypt.GenerateSalt(12));
}
This is how I'm handling 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 wraps the call to BCrypt.CheckPassword:
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 is doing:
public static bool CheckPassword(string plaintext, string hashed)
{
return StringComparer.Ordinal.Compare(hashed, HashPassword(plaintext, hashed)) == 0;
}
So, yeah...I dunno what's going on, but what I do know, is that my boolean authorized
variable in my Login controller action always returns false for some reason.
I've used this exact same BCrypt class on at least a couple of other projects in the past, and never had any problems with it at all. Is ASP.NET MVC 3 doing some weird, different encoding to posted data that I'm missing or need to handle differently or something? Either that, or is SQL CE 4 doing it (that's the datastore I'm currently using)? Everything seems to be in order in my code from what I can tell, but for some reason, password checking is failing every time. Anyone have any ideas?
Thanks.
UPDATE: Here's the code comments included with the BCrypt class with examples of how it's 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's 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>