I am using asp.net core razor engine. I have a hash for a password that is saved into my db. How do I compare that to when the user enters their password.
Here is the method where I hashed the password.
[HttpPost]
[Route("")]
public IActionResult Register(Home model)
{
if(!ModelState.IsValid)
{
return View("Index", model);
}
PasswordHasher<Home> Hasher = new PasswordHasher<Home>();
model.Password = Hasher.HashPassword(model, model.Password);
userFactory.Add(model);
TempData["message"] = false;
return RedirectToAction("Index");
}
The app retrieved the users password based on the email they give when they register. What I cannot figure out is how to compare the hashed password to the password the user gives.
Why does your password hasher need the model type and the model itself as inputs? What you want to do is use a hasher that always returns the same result given the same password input (or, these days, given the same two inputs: a password and a randomly generated SALT that you store alongside the password)
Here's how to do it properly: https://crackstation.net/hashing-security.htm
In other words, throw away your current hasher and make one that just takes the password (and ideally salt) as an input. You hash a newly created password with the exact same lines of code as you hash a "logging in" password - then you compare the logging-in hash to the stored hash, to see if they match.
Related
I am writing an ASP.NET program where I need to store the users password in the database. But I get a password mismatched when I Compare the password from the database with the user input password. Even if the users password is correct.
Password Hashing:
string PasswordSalt = Crypto.HashPassword(DateTime.Now.ToString());
string hashPassword = Crypto.HashPassword(formcollection["PassWord"]); //Hash User PassWord
user.PassWord = Crypto.HashPassword(PasswordSalt + hashPassword);//Add Salt to Password For Futher Security
user.PassWordSalt = PasswordSalt;
Password Verification:
Users ThisUser = Users.UsersGetByEmail((string)Session["email"]);
string checkpassword = ThisUser.PassWord;
//User Inputed password.
string password = user.PassWord;
if (password != null)
{
//Need to fix.
string encrypt_password = Crypto.HashPassword(password);
string salted_password = Crypto.HashPassword(ThisUser.PassWordSalt + encrypt_password);
//bool does_password_match = Crypto.VerifyHashedPassword(checkpassword, password);
if (checkpassword == salted_password)
{
//Check if the inputed password matches the password from the Database.
//Remember to give session based on the user_id.
Session["user_id"] = ThisUser.Id;
return RedirectToAction("Promise");
}
else
{
ModelState.AddModelError("PassWord", "Wrong Password, Please Enter Correct Password");
return View(user);
}
I've never used it, but based on the documentation...
Crypto.HashPassword adds the salt for you and returns a base-64 encoded string with all the details in it to verify the password. So, you do NOT need to add a salt yourself.
All you need to do is store the hash result (base64EncodedHash below) in the DB, and then use it with VerifyHashedPassword to authenticate later. E.g. make a unit test like so:
var base64EncodedHash = Crypto.HashPassword("password");
Assert.IsTrue( Crypto.VerifyHashedPassword( base64EncodedHash, "password" ) );
Assert.IsFalse( Crypto.VerifyHashedPassword( base64EncodedHash, "otherPass") );
https://msdn.microsoft.com/en-us/library/system.web.helpers.crypto.verifyhashedpassword(v=vs.111).aspx
To translate this to your code:
user.PassWord = Crypto.HashPassword(formcollection["PassWord"]);
Then to verify (comments added for quirks I see):
//Why are you storing "email" in Session before user is validated??? Seems off.
Users ThisUser = Users.UsersGetByEmail((string)Session["email"]);
string userInputPassword = user.PassWord; //this should be coming from POST
if( ThisUser != null && Crypto.VerifyHashedPassword(ThisUser.PassWord, userInputPassword) ) {
Session["user_id"] = ThisUser.Id;
return RedirectToAction("Promise");
}
else {
ModelState.AddModelError("PassWord","Your username or password are incorrect");
return View(user);
}
Ideally, as I somewhat indicated by my change of your error text...you also want to give the user the same error message whether the username/email or password are wrong. Your code, as is, probably returns a different error if the email doesn't return an account, but you don't want to give that much info to brute-force attackers.
You also need to put in some brute-force checking so that if they attempt too many times with failures, block that IP address for X amount of time., etc.
And, as someone said...when it comes to security...until you're the
expert...it's best to use pre-existing code/frameworks to mitigate you
risks.
I have really strange problem with ASP.NET Identity and EntityFramework.
I have a login form, from which I receive username and password.
Then I check if the user exist in the database.
After that I call the UserManager's method VerifyHashedPassword to verify that the user password from database and that from the form are the same. Everything is OK, but for some of the users in the database, the method give me result that the given password and the hashed password are not the same (but they actually are). I just can't figure out why for some of the users password verification fails.
Here's my code.
public async Task<User> FindUserAsync(string userName, string password)
{
User user;
if (password != null)
{
user = await _userManager.FindByNameAsync(userName);
if (user == null)
{
user = await _userManager.FindByEmailAsync(userName);
}
var result = _userManager.PasswordHasher.VerifyHashedPassword(user.PasswordHash, password);
if (!(result == PasswordVerificationResult.Success))
{
return null;
}
}
return user;
}
Well, the problem was on my side.
The problem came when some user decide to change their password.
If user change their password, I first delete the password and then I add the new password to the database. Which was very dumb, because the transaction to the database with which I delete the password is committed successfully, but the transaction (for some reason) with which I add the new password was never done successfully.
Actually I used these two methods when a password is changed:
await _userManager.RemovePasswordAsync(appUser.Id);
await _userManager.AddPasswordAsync(appUser.Id, fbUser.Password);
Now I do that manually:
String hashedNewPassword = _userManager.PasswordHasher.HashPassword(fbUser.Password);
if (hashedNewPassword != null)
{
appUser.PasswordHash = hashedNewPassword;
_context.Entry(appUser).State = EntityState.Modified;
await _context.SaveChangesAsync();
}
And this works for me.
You can do this using the UserManager as long as you are able to do it async, the non-async extension method was broken for me at least, sending a null to the hashedPassword argument in the PasswordHasher. To do it async:
await _userManager.ChangePasswordAsync(userId, oldPassword, newPassword);
I'm working on an intranet, I've just added a feature on the user's profile to change his password.
As you can see with the following controller :
[HttpPost]
public ActionResult ChangePassword(Employee objToEdit, FormCollection form, LocalPasswordModel model) // Find how to obtain "OldPassword" from AccountModel
{
objToEdit.Login = User.Identity.Name;
string name = objToEdit.FirstName;
string pwd = form["NewPassword"];
string confirm = form["ConfirmPassword"];
if (_service.Edit_password(objToEdit, pwd, confirm)) // Checks if NewPassword and ConfirmPassword are the same, and does some syntax checking
{
bool changePasswordSucceeded;
try
{
changePasswordSucceeded = WebSecurity.ResetPassword(WebSecurity.GeneratePasswordResetToken(objToEdit.Login), pwd); // Seems to work
}
catch (Exception)
{
changePasswordSucceeded = false;
}
if (changePasswordSucceeded)
{
return RedirectToAction("Index", new { Message = CRAWebSiteMVC.Controllers.AccountController.ManageMessageId.ChangePasswordSuccess });
}
else
{
ModelState.AddModelError("", "The current password is incorrect or the new password is invalid.");
}
return new RedirectResult(Url.Action("Index"));
}
return View();
}
So far, the user just needs to input a New password and a confirmation password. I wish to add a "Enter your current Password" feature but I can't find a way to retrieve the user's current password !
The user profile DB does not contain a Password column anymore and I use Form authentication if that's of any help.
EDIT: Thank you for your help, to solve my problem I simply replaced the ResetPassword line by the following :
changePasswordSucceeded = WebSecurity.ChangePassword(objToEdit.Login, current, pwd);
If it fails, it directly displays the error message that the current password is wrong.
You can't !
That's actually a security feature. You should never store a password in plain text.
The good thing is, you don't need to do the comparison yourself:
Instead, use something like ValidateUser to let the Membership Provider validate the provided password. Behind the scenes, this method will hash the password and compare it with the hashed version contained in the database.
EDIT:
Also, note that since you are using the WebSecurity class, there is a method, ChangePassword that accepts the current password. It seems that method will check the current password matches the specified currentPassword parameter. Maybe you should use this one instead of ResetPassword
I am using the following code to generate a reset Token
var token = WebSecurity.GeneratePasswordResetToken(user);
it worked the first time, in that it updated the webpages_Membership table, and updated the following fields,
PasswordVerificationToken
PasswordVerificationTokenExpirationDate
but when I run the above code again, the token returned is the same, and nothing is updated.
how does it work?
The token will only refresh once it has been used with WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword);. If you do not complete the full password reset process, the same token will always be generated for that specific user.
If the password is not reset within 24 hours (default), then the above method will return false. If you want to override the default password expiration, you can add the optional parameter tokenExpirationInMinutesFromNow when calling WebSecurity.GeneratePasswordResetToken :
public static string GeneratePasswordResetToken(
string userName,
int tokenExpirationInMinutesFromNow
)
If you want to catch a bad password change attempt, just wrap WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword); in a try catch block, and you'll be able to display an error to the user :
[HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
public ActionResult ForgotPassword(ForgotPasswordModel model)
{
if (ModelState.IsValid)
{
try
{
//Reset password using the reset token and the new password
WebSecurity.ResetPassword(model.ResetToken, model.TheUsersNewPassword);
//Redirect to the home account page.
return RedirectToAction("Index", "Home");
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, LocalizedText.Account_Reset_Password_Error);
}
}
//Something bad happen, notify the user
return View(model);
}
I won't worry to much about catching a specific exception here, because the fix will be the same either way - they'll need to generate another password reset token.
Microsoft is coming up with a new Membership system called ASP.NET Identity (also the default in ASP.NET MVC 5). I found the sample project, but this is not implemented a password reset.
On password reset topic just found this Article: Implementing User Confirmation and Password Reset with One ASP.NET Identity – Pain or Pleasure, not help for me, because do not use the built-in password recovery.
As I was looking at the options, as I think we need to generate a reset token, which I will send to the user. The user can set then the new password using the token, overwriting the old one.
I found the IdentityManager.Passwords.GenerateResetPasswordToken / IdentityManager.Passwords.GenerateResetPasswordTokenAsync(string tokenId, string userName, validUntilUtc), but I could not figure out what it might mean the tokenId parameter.
How do I implement the Password Reset in ASP.NET with MVC 5.0?
I get it: The tokenid is a freely chosen identity, which identifies a password option. For example,
1. looks like the password recovery process, step 1
(it is based on: https://stackoverflow.com/a/698879/208922)
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
//[RecaptchaControlMvc.CaptchaValidator]
public virtual async Task<ActionResult> ResetPassword(
ResetPasswordViewModel rpvm)
{
string message = null;
//the token is valid for one day
var until = DateTime.Now.AddDays(1);
//We find the user, as the token can not generate the e-mail address,
//but the name should be.
var db = new Context();
var user = db.Users.SingleOrDefault(x=>x.Email == rpvm.Email);
var token = new StringBuilder();
//Prepare a 10-character random text
using (RNGCryptoServiceProvider
rngCsp = new RNGCryptoServiceProvider())
{
var data = new byte[4];
for (int i = 0; i < 10; i++)
{
//filled with an array of random numbers
rngCsp.GetBytes(data);
//this is converted into a character from A to Z
var randomchar = Convert.ToChar(
//produce a random number
//between 0 and 25
BitConverter.ToUInt32(data, 0) % 26
//Convert.ToInt32('A')==65
+ 65
);
token.Append(randomchar);
}
}
//This will be the password change identifier
//that the user will be sent out
var tokenid = token.ToString();
if (null!=user)
{
//Generating a token
var result = await IdentityManager
.Passwords
.GenerateResetPasswordTokenAsync(
tokenid,
user.UserName,
until
);
if (result.Success)
{
//send the email
...
}
}
message =
"We have sent a password reset request if the email is verified.";
return RedirectToAction(
MVC.Account.ResetPasswordWithToken(
token: string.Empty,
message: message
)
);
}
2 And then when the user enters the token and the new password:
[HttpPost]
[ValidateAntiForgeryToken]
[AllowAnonymous]
//[RecaptchaControlMvc.CaptchaValidator]
public virtual async Task<ActionResult> ResetPasswordWithToken(
ResetPasswordWithTokenViewModel
rpwtvm
)
{
if (ModelState.IsValid)
{
string message = null;
//reset the password
var result = await IdentityManager.Passwords.ResetPasswordAsync(
rpwtvm.Token,
rpwtvm.Password
);
if (result.Success)
{
message = "the password has been reset.";
return RedirectToAction(
MVC.Account.ResetPasswordCompleted(message: message)
);
}
else
{
AddErrors(result);
}
}
return View(MVC.Account.ResetPasswordWithToken(rpwtvm));
}
Skeleton proposal to sample project on github, if anyone needs it may be tested.The E-mail sending not yet written, possibly with the addition soon.
Seems like a lot of trouble...
What advantage does the above give over:
the user clicking a 'Recover Account' link
this sends an 64 byte encoded string of a datetime ticks value (call it psuedo-hash) in an email
click the link back in the email to a controller/action route that
matches email and it's source server to psuedo-hash, decrypts the psuedo-hash, validates the time since sent and
offers a View for the user to set a new password
with a valid password, the code removes the old user password and assigns the new.
Once complete, successful or not, delete the psuedo-hash.
With this flow, at no time do you EVER send a password out of your domain.
Please, anyone, prove to me how this is any less secure.