It may not be believable considering how often even to this day plain-text passwords get leaked, but the history of the password hash is a long one. Way back when I was just starting web dev 22 some years ago, the common practice was when a user creates an account you hash their password and store that value. Then every time they log in you take what they give you, hash it, see if the hash matched what you stored.
At that time getting access to crypto libs from web applications was pretty painful and it wasn't uncommon that we would be like "eh, fukkit" and end up using MD5 or SHA to hash before storing. Fast forward over time and really all I ever did to our systems was update which version of SHA was being used, slowly migrating to longer and longer hashes, adding in salts (which my system called Shifting Sands). The problem that was trying to solve was not so much making it harder to guess a password but make it harder to have your dataset match a rainbow table in the event the database was compromised.
A completely moot point as if someone does have a copy of the data locally it is pretty trivial to blaze through a huge dictionary on modern CPU and GPUs.
The main point of crypto designed hashing is for the most part, really just to be slow on purpose. If you do get dumped it is better that you maybe get a week to find out and deal before live auth gets violated, rather than like the 20 seconds it probably takes otherwise with simple hashes. PHP has had password_hash and password_verify since 5.5.0 (2013), but I don't think it really became a strong contender until PHP 7.2 (2017) when support for the Argon algo was added. With the ease of access now to a quality algo, not using it is be pretty embarrassing.
In a hash based auth system that has been what I would consider, engineered and abstracted to the proper level, there should really only be two points of contact where modifications should be needed. So how much effort was needed to stop being lazy and actually get this done?

User Creation / Password Updating

When an account is created in the system you need things like their email, username, and the password they want to use. After you run basic checks on their password like, is it long enough, does it contain enough character variation that you demand, you would then generate the hash and insert it into the dataset. The short of it is you should have one function that takes a String Password, and gives you back a String Hash. In this case it it was our User::Insert method's subfunction, User::Insert_ValidatePassword. Additionally when you change your password the Password app actually reuses the User::Insert_ValidatePassword method so we're really down to a single point for hash generation.
// old code

$Opt->PHash = hash('sha512',$Opt->Password1);

// new code
// rather than remember the names of the algos it was added to the config file.

$Opt->PHash = password_hash(
1 line of code, user creation and password updating is done*

User Log In

The second point of contact is allowing the user to log in with their password. We already updated the generation of these hashes so we are left with validation of a password against it. When they hit our login form we need their user name/id and their password. Assuming they gave us a valid user our user object then has a single method User::IsValidPassword.
* I didn't mention it before but when a user changes their password, it also uses the User::IsValidPassword method as we ask for the old password before allowing the new one to be changed - even if the user already has a valid session.
// old code

return hash('sha512',$Password) === $this->PHash;

// new code.

return password_verify($Password,$this->PHash);
1 line of code, user auth is done.


Two lines of code and if we are setting up a new project from fresh we're technically done. But if you already have a database full of old hashes, none of the users are going to be able to log in anymore. So two things need to be added, a fallback to the old hash() if we infact still have an old hash for this user, and a way to update the the hash to a new version automatically. I elected to modify the User::IsValidPassword method again to add the fallback as well as force a hash update automatically if needed.
public function
IsValidPassword(String $Password):
Bool {
@date 2017-02-11
@updated 2020-10-02
does the specified password match the one belonging to this user?

	$Valid = FALSE;
	$Algo = Nether\Option::Get('Atlantis.User.Password.Algo');


	throw new Exception('cannot validate hash from saftey instance');

	// using the php password api

	if(strpos($this->PHash,'$') === 0)
	$Valid = password_verify($Password,$this->PHash);

	// falling back to the old old old.

	$Valid = (hash('sha512',$Password) === $this->PHash);

	// if the password needs rehashed because we changed the server
	// settings then lets do it now since we have it in our grasp.

	if($Valid && password_needs_rehash($this->PHash,$Algo))
	$this->Update([ 'PHash' => password_hash($Password,$Algo) ]);

	return $Valid;
If our hash begins with a dollar sign that means it was generated as a crypto format rather than just being our flat SHA512 so that can be used to check which method of validation to use. Instead of just instantly returning the success or fail of this check we make a note of it.
If and only if the password was valid, we introduce a check for password_needs_rehash, which will return true if the hashes are not crypto hashes (so our SHA512's) or if the options for computation has changed since the hash was generated. If it requests a rehash while we have their raw password, generate and push it in now just get it done.
Unfortunately we cannot just bulk update all users - you need to have the actual original password to generate the updated hash. You better not have stored a copy of that in plain text anywhere... but now any time we need to validate a password for any reason user accounts will get updated. If felt it was important that all users update their hashes ASAP, we would probably just flat out blank out all old hashes and force a user to use the `Forgot Your Password?` feature of the login system to confirm their identity and generate a new login hash (with I mean, an email explaining this in detail).

Taking it A Little Farther

Both password_hash and password_needs_rehash have a third argument where additional options can be passed. PHP's default options if you do not specify any (we did not in any of the examples) are decent, but if you can increase any of the settings from their defaults that increase the time it takes to calculate a hash, you are only making it "better" - when calculating a hash we talk about its 'cost' or how hard it is for the computer to finish it. Be sure to take a look at the available options to evaluate if you can accept the additional costs of computation.
People considering themselves academics will point out there is potentially a "timing attack" involved with your login system, where, with microtime measurements, the attack can determine if a user even exists in your database based on the time it took to check, return, and then attempt a password validation. What happens is if it finds a user, and then attempts validation, it is going to take (to what microprocessors consider) a much longer time to return an error, than if you found no user at all and immediately returned. Is the solution, if no user is found, crypt some random ass string just to waste time? This was not the problem I am attempting to solve immediately.


I waited way too long to update something that was trivial to update. If you been sitting on this just do it already.