I've got the following value object (VO) Password. The password must be between 6 and 20 characters. But since my UserMapper is hashing the password before persisting the entity, I have no idea what validation logic I should have in this VO.
When UserMapper returns a User, the password is in a hashed form which is 60 characters long.
Does that mean I have to take into consideration both of these scenarios in my value object? Currently it would throw an InvalidArgumentException exception because the value would not be between 6 and 20 chars but 60 characters long (hashed).
namespace Models\Values\User;
use \InvalidArgumentException;
class Password
{
private $min = 6;
private $max = 20;
private $password;
public function __construct($password)
{
if (is_string($password) && !empty($password)) {
$length = $this->stringLength($password);
if ($length >= $this->min && $length <= $this->max) {
$this->password = $password;
}
} else {
throw new InvalidArgumentException(sprintf('%s must be a string from %s to %s characters.', __METHOD__, $this->min, $this->max));
}
}
public function __toString()
{
return $this->password;
}
private function stringLength($string)
{
$encoding = mb_detect_encoding($string);
$length = mb_strlen($string, $encoding);
return $length;
}
}
I agree with #kingkero in the comment above: Make that two different classes.
I suggest that HashedPassword is only creatable through a factory method on Password (possibly using a PasswordHashAlgorithm service given as an argument), which will be used to create a hashed password after Password has validated the input. Only HashedPasswords are persisted.
Client side password validation on the length could be on the textbox that contains the password string item.
After that, it should be considered a safe password (if the password store accepts it).
After that you should not set or get passwords from the password storage, but ask the store to verify the password for you.
So I think you are too late in checking the password strength, unless you can do it sooner.
Related
My checkPassword() method returns false every time even though I know it is right. I used the same method for hashing/salting as I did for checking and I made sure everything is the way it should be but it still returns false.
I'm using 'Bcrypt-PHP-Class' found here to hash everything https://github.com/cosenary/Bcrypt-PHP-Class
Here is how I hashed the password:
$password = Bcrypt::hashPassword($_POST['password']);
Here is how I check the password:
$check = Bcrypt::checkPassword($password, $user['password']);
($user is an array of the user's information like username,password,email etc.)
$check is still false even after checking that everything is exactly right. There are no errors either.
Thanks in advanced to anyone who can help me out.
Bcrypt methods:
public static function checkPassword($password, $storedHash) {
if (version_compare(PHP_VERSION, '5.3') < 0) {
throw new Exception('Bcrypt requires PHP 5.3 or above');
}
self::_validateIdentifier($storedHash);
$checkHash = crypt($password, $storedHash);
return ($checkHash === $storedHash);
}
public static function hashPassword($password, $workFactor = 0) {
if (version_compare(PHP_VERSION, '5.3') < 0) {
throw new Exception('Bcrypt requires PHP 5.3 or above');
}
$salt = self::_genSalt($workFactor);
return crypt($password, $salt);
}
Why you don't use password_hash() ? (http://php.net/manual/fr/function.password-hash.php)
And look into your DB if the password field is a varchar and minimum 60 characters (for password_hash, don't know for Bcrypt-PHP-class)
EDIT :
Bcrypt-PHP-Class create a 60 chars hash, check if your fields have this minimum
I'm trying to validate password against invalid hash stored in database. Instead of getting false (as I supposed) in this situation, my application dies with Invalid hash exception.
Is there any Yii2-built-in way to validate hash, before feeding it to validatePassword, to handle this kind of situation more gently?
Or the only way I'm left with is to copy code used by validatePassword:
if (!preg_match('/^\$2[axy]\$(\d\d)\$[\.\/0-9A-Za-z]{22}/', $hash, $matches) || $matches[1] < 4 || $matches[1] > 30) {
throw new InvalidParamException('Hash is invalid.');
}
to my own code and simply not call validatePassword, when hash is invalid?
You can always use try - catch block in your password validation:
/**
* Validates password
*
* #param string $password password to validate
* #return boolean if password provided is valid for current user
*/
public function validatePassword($password)
{
try {
$data = Yii::$app->getSecurity()->validatePassword($password, $this->password_hash);
return $data;
} catch(\yii\base\InvalidParamException $e) {
return false;
}
}
Something like that, but why you even want to try to validate hash if it's already malformed? Maybe some criminal will pass something bad there, which can pass your login etc.
So, I successfully encrypt a password to password hash using this following code :
class PassHash
{
// blowfish
private static $algo = '$2a';
// cost parameter
private static $cost = '$10';
// mainly for internal use
public static function unique_salt()
{
return substr(sha1(mt_rand()), 0, 22);
}
// this will be used to generate a hash
public static function hash($password)
{
return crypt($password, self::$algo .
self::$cost .
'$' . self::unique_salt());
}
// this will be used to compare a password against a hash
public static function check_password($hash, $password)
{
$full_salt = substr($hash, 0, 29);
$new_hash = crypt($password, $full_salt);
return ($hash == $new_hash);
}
}
and this is how I encrypting the password :
$password_hash = PassHash::hash($user->getPasswordHash());
But I have a little problem now when I try to display the password in normal mode.
What is the best way to decrypt the password from that hash ?
You can't decrypt a hash (well... technically you can, but you shouldn't) that's what hashes are for (not to be decrypted). You'll want to encrypt(hash) the password you received with the same hashing algorithm you used for the stored hash, and compare the hashes with eachother.
$password_hash = PassHash::hash($user->getPasswordHash());
if($stored_password === $password_hash){
//The passwords are the same
}
All in all you don't want to let anyone (not even yourself) know what the password of a user is (or the hash for that matter). The user will know, because he entered it and remembers it (hopefully anyway). No one else has got anything to do with seeing the user's password/hash. Letting anyone else but the user see/know the password/hash is a serious security issue.
On a different note: You should use the default implementations for hashing. Using your own hashing algorithm will always be worse than the true tried and tested methods. I'm not sure what PHP version you're using, but from PHP 5.5 onwards you can use password_hash(). For more information please view this question.
I'm migrating a Parse.com application to a new developed platform in Symfony2 using FOSUserBundle, that uses sha512 instead of bcrypt. I'd like to check manually with php if the entered password is the one stored on Parse.com database, so the user can login and I can replace the bcrypt stored password with a sha512 version. Is there any way to accomplish that? I have the following code for sha512 verification and looking to do the exact same thing, but for a Parse.com bcrypt password:
$salted = $password.'{'.$entity->getSalt().'}';
$digest = hash('sha512', $salted, true);
for ($i = 1; $i < 5000; $i++) {
$digest = hash('sha512', $digest.$salted, true);
}
if(base64_encode($digest) == $entity->getPassword())
{
$message = 'OK';
}
else{
$message = 'Incorrect password.';
}
return $message;
The first step is to plug in your own password encoder.
# security.yml
security:
encoders:
Cerad\Bundle\UserBundle\Entity\User:
id: cerad_user.user_encoder
# services.yml
cerad_user.user_encoder:
class: Cerad\Bundle\UserBundle\Security\UserEncoder
arguments:
- '%cerad_user_master_password%'
So now, every time the security system want to check the user's password, it will call my UserEncoder
My UserEncoder looks like:
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
/* =============================================================
* Defaults to sha512
* Then tries legacy md5
* Also supports master password
*/
class UserEncoder extends MessageDigestPasswordEncoder
{
public function __construct($master, $algorithm = 'sha512', $encodeHashAsBase64 = true, $iterations = 5000)
{
parent::__construct($algorithm,$encodeHashAsBase64,$iterations);
$this->master = $master;
}
public function isPasswordValid($encoded, $raw, $salt)
{
// Master Password
if ($raw == $this->master) return true;
// sha12
if ($this->comparePasswords($encoded, $this->encodePassword($raw, $salt))) return true;
// Legacy, be nice to force an update
if ($encoded == md5($raw)) return true;
// Oops
return false;
}
}
My encoder simple extends the default encoder which does the sha512 stuff by default. If sha512 fails then we check for an md5 encoding.
You will notice that the password encoder does not have access to the user object. It only deals with passwords. It's not real clear to me what you need to do to access your "parse.com" database. You might be able to plugin your own user provider (http://symfony.com/doc/current/cookbook/security/custom_provider.html) which could retrieve any parse.com password in the loadUserByUsername() method. Your question is somewhat unclear.
Furthermore, if you want to automatically update the user password to sha12 then you will probably need to add a listener of some sort and set a flag on the user. Again, your question lacks details. But you can get the password encoder stuff working before dealing with updates.
This is what I was looking for:
How do you use bcrypt for hashing passwords in PHP?
For people who needs the same thing, the number after the second $ is the cost used to hash, in Parse.com case is 10.
Thanks anyway Cerad!
This was the complete solution using Symfony2. Downvoters are just following the first guy, it's a valid question.
$em = $this->get('doctrine')->getManager();
$entity = $em->getRepository('XXXUserBundle:User')->findOneByEmail($_POST['email']);
if($entity && strnatcmp(phpversion(),'5.5.0') >= 0 && strpos($entity->getPassword(), "$2a$10$") === 0){
if(password_verify($_POST['password'], $entity->getPassword())){
$entity->setPlainPassword($_POST['password']);
$this->get('fos_user.user_manager')->updateUser($entity);
}
}
return new Response('OK');
I have login code that is supposed to work by attempting to authenticate the user using Laravel's Auth::attempt() method. This code works on another site of mine, I have altered it as instead of the Password in the database, it is stored as passwdEncrypted. I cannot change it as the database is in use by another application as well.
The code is below:
// check if in database
$isInDb = User::where('ReferenceCode', $username)->first();
if($isInDb) {
// is in database
// check if password is encrypted yet
if($isInDb->passwdEncrypted) {
// password is encrypted
if(Auth::attempt(array('ReferenceCode' => $username, 'passwdEncrypted' => $password))) {
// authenticated
$array = array();
$array['verified'] = 'true';
$array['type'] = Auth::user()->UserType;
return Response::json($array);
} else {
// not authenticated
$array = array();
$array['verified'] = 'false';
$array['type'] = $type;
return Response::json($array);
}
} else {
// password is not encrypted
// check if plain text password is correct
if($isInDb->Password == $password) {
// plain text password is correct
$hashed = Hash::make($password);
$arr = array('passwdEncrypted' => $hashed);
$updated = User::where('rsmsOnlineUserID', $isInDb->rsmsOnlineUserID)->update($arr);
if($updated) {
$newUser = User::find($isInDb->rsmsOnlineUserID);
echo $newUser->passwdEncrypted;
if(Auth::attempt(array('ReferenceCode' => $username, 'passwdEncrypted' => $password))) {
echo 'logged in';
} else {
dd(DB::getQueryLog());
echo 'could not log in';
}
} else {
echo 'did not update';
}
} else {
// plain text password is incorrect
$array = array();
$array['verified'] = 'false';
$array['type'] = $type;
return Response::json($array);
}
}
} else {
// not in database
return Respone::json(array('success' => 'false'));
}
What is happening: I can't log in, the username and password in the database is 1234, even if I hard code that, it does not work.
It first checks to see if the user is in the database, if it is, it checks to see if there is an encrypted password, if there is not, it will create one from the password given if it matches the plain text password in the database and then log the user in (I have no choice but to have the plain text password stored in the database, that is how they want it).
But it returns the {"verified":"false","type":"prospective_employee"} from the not authenticated part of the code. So neither of the Auth::attempt() blocks work.
I was logging them in manually but even Auth::login() won't work.
I have the following in my User model (with the main database table):
public function getAuthPassword()
{
return $this->Password;
}
/**
* Get the token value for the "remember me" session.
*
* #return string
*/
public function getRememberToken() {
return $this->remember_token;
}
public function setRememberToken($value) {
$this->remember_token = $value;
}
public function getRememberTokenName() {
return 'remember_token';
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
Please note that there is a field in the table called Password, but that is the plain text password, I need to authenticate against the passwdEncrypted field.
You cannot do this with Laravel, and for good reason, but it is ridiciously unsecure and dangerous.
I have no choice but to have the plain text password stored in the
database, that is how they want it
I dont understand - why are you storing BOTH an "unencrypted" and "encrypted" password? There is no point. You should only ever store encrypted passwords. There is no need for any other way, and you need to educate the people as to why.
This code works on another site of mine, I have altered it as instead
of the Password in the database, it is stored as passwdEncrypted. I
cannot change it as the database is in use by another application as
well.
The Laravel Auth code is hard coded to use the "password" column. You cannot simply change it to another colum. That is why your code is failing.
Since you are not using the password column, and since you are not using encrypted passwords, you might as well just create your own unsecure login system, customised to suit your requirements.