I'm wondering if this function (which is in part taken from a ~2 year old phpBB version), is good enough.
If not, why?
And how would you change it (making the transition seamless for existing users) ?
The result of hash_pwd() is what will be saved in a DB.
function hash_pwd($password) { $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; $random_state = $this->unique_id(); $random = ''; $count = 6; if (($fh = @fopen('/dev/urandom', 'rb'))) { $random = fread($fh, $count); fclose($fh); } if (strlen($random) < $count) { $random = ''; for ($i = 0; $i < $count; $i += 16) { $random_state = md5($this->unique_id() . $random_state); $random .= pack('H*', md5($random_state)); } $random = substr($random, 0, $count); } $hash = $this->_hash_crypt_private($password, $this->_hash_gensalt_private($random, $itoa64), $itoa64); if (strlen($hash) == 34) { return $hash; } return false; } function unique_id() { $val = microtime(); $val = md5($val); return substr($val, 4, 16); } function _hash_crypt_private($password, $setting, &$itoa64) { $output = '*'; // Check for correct hash if (substr($setting, 0, 3) != '$H$') { return $output; } $count_log2 = strpos($itoa64, $setting[3]); if ($count_log2 < 7 || $count_log2 > 30) { return $output; } $count = 1 << $count_log2; $salt = substr($setting, 4, 8); if (strlen($salt) != 8) { return $output; } /** * We're kind of forced to use MD5 here since it's the only * cryptographic primitive available in all versions of PHP * currently in use. To implement our own low-level crypto * in PHP would result in much worse performance and * consequently in lower iteration counts and hashes that are * quicker to crack (by non-PHP code). */ if (PHP_VERSION >= 5) { $hash = md5($salt . $password, true); do { $hash = md5($hash . $password, true); } while (--$count); } else { $hash = pack('H*', md5($salt . $password)); do { $hash = pack('H*', md5($hash . $password)); } while (--$count); } $output = substr($setting, 0, 12); $output .= $this->_hash_encode64($hash, 16, $itoa64); return $output; } function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6) { if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) { $iteration_count_log2 = 8; } $output = '$H$'; $output .= $itoa64[min($iteration_count_log2 + ((PHP_VERSION >= 5) ? 5 : 3), 30)]; $output .= $this->_hash_encode64($input, 6, $itoa64); return $output; } function _hash_encode64($input, $count, &$itoa64) { $output = ''; $i = 0; do { $value = ord($input[$i++]); $output .= $itoa64[$value & 0x3f]; if ($i < $count) { $value |= ord($input[$i]) << 8; } $output .= $itoa64[($value >> 6) & 0x3f]; if ($i++ >= $count) { break; } if ($i < $count) { $value |= ord($input[$i]) << 16; } $output .= $itoa64[($value >> 12) & 0x3f]; if ($i++ >= $count) { break; } $output .= $itoa64[($value >> 18) & 0x3f]; } while ($i < $count); return $output; }
password_hash() creates a new password hash using a strong one-way hashing algorithm. The following algorithms are currently supported: PASSWORD_DEFAULT - Use the bcrypt algorithm (default as of PHP 5.5.
Password hashing add a layer of security. Hashing allows passwords to be stored in a format that can't be reversed at any reasonable amount of time or cost for a hacker. Hashing algorithms turn the plaintext password into an output of characters of a fixed length.
Whereas the transmission of the password should be encrypted, the password hash doesn't need to be encrypted at rest. When properly implemented, password hashing is cryptographically secure. This implementation would involve the use of a salt to overcome the limitations of hash functions.
It is a one-way algorithm, in that you don't decrypt it to validate it, you simply pass the original string in with your password and if it generates the same hash for the provided password, you're authenticated. It's best to omit the salt and let it generate one for you.
The code that you have given is a port of PHPASS, specifically the "portable" algorithm. Note the qualification of portable
. This will only apply to the phpass
library if you pass true
as the second constructor parameter. From here on out in this answer, phpass
refers ONLY to the portable algorithm, and not the library itself. The library will do bcrypt by default if you do not explicitly specify portable
...
The PHPBB team did not develop this themselves (a very good thing), but ported it from phpass directly (arguable).
There are a few questions we should ask here:
The short answer is no, it's not bad. It offers pretty good security. If you have code on this right now, I wouldn't be in a rush to get off it. It's adequate for most usages. But with that said, there are far better alternatives if you were starting a new project that I wouldn't pick it.
Relative To pbkdf2
: The phpass
algorithm uses hash()
where pbkdf2()
uses hash_hmac()
. Now, a HMAC runs 2 hashes for every call internally, but the PHP implementation only takes approximately 1.6 times the execution of a single call to hash()
(isn't C wonderful?). So we get 2 hashes from hash_hmac
in 62% of the time it would take hash()
to execute 2 hashes.
What does that mean? Well, for a given runtime, pbkdf2
will run approximately 37.5% more hashes than the phpass
algorithm. More hashes in a given time == good, because it results in more computation being performed.
So pbkdf2
is approximately 37.5%
stronger than phpass
when using the same primitive (md5
in this case). But pbkdf2
can also take stronger primitives. So we can use pbkdf2
with sha512
to gain a very significant advantage over the phpass
algorithm (mainly because sha512
is a harder algorithm with more computation than md5
).
This means that not only is pbkdf2
able to generate more computations in the same amount of time, it's able to generate harder computations.
With that said, the difference isn't overly significant. It's very much measurable, and pbkdf2
is definitely "stronger" than phpass
.
Relative To bcrypt
: This is a lot harder of a comparison to make. But let's look at the surface of it. phpass
uses md5
, and a loop in PHP. pbkdf2
uses any primitive (in C) and a loop in PHP. bcrypt
uses a custom algorithm all in C (meaning it's a different algorithm from any available hash). So right of the bat, bcrypt
has a significant advantage just do to the fact that the algorithm is all in C. This allows for more "computation" per unit time. Thereby making it a more efficient slow algorithm (more computations in the given runtime).
But just as important as how many computations it does is the quality of the computations. This could an entire research paper, but in short it comes down to the fact that the computations that bcrypt uses internally are much harder to perform than a normal hash function.
One example of the stronger nature of bcrypt
is the fact that bcrypt
uses a far larger internal state than a normal hash function. SHA512
uses a 512 bit internal state to compute against a block of 1024 bits. bcrypt
uses instead about 32kb of internal state to compute against a single block of 576 bits. The fact that bcrypt
's internal state is so much bigger than SHA512
(and md5
and phpass
) partially accounts for the stronger nature of bcrypt
.
For new projects, absolutely. It's not that it's bad. It isn't. It's that there are demonstrably stronger algorithms out there (by orders of magnitude). So why not use them?
For further proof of how bcrypt
is stronger, check out the Slides from Password13 (PDF) which launched a 25 GPU cluster for cracking password hashes. Here are the relevant results:
md5($password)
sha1($password)
md5crypt
(which is very similar to phpass with a cost of 10): bcrypt
with a cost of 5
Note: all possible 8 character passwords are using a 94 character set:
a-zA-Z0-9~`!@#$%^&*()_+-={}|[]\:";'<>,.?/
So if you're writing new code, without a doubt use bcrypt
. If you have phpass
or pbkdf2
in production now, you may want to upgrade, but it's not a clear cut "you're significantly vulnerable".
Quick answer:
Use bcrypt (when it is ready) or password_compat library from ircmaxell - it's a bcrypt library.
Long answer:
This is too complex and outdated for current technology. Md5 should be avoided and just salting isn't good enough. Use bcrypt and save yourself the headache.
You might ask yourself why that specific library? Well the same functions will be available in php 5.5 so you won't have to change any coding. Best of luck and keep it simple and efficient. Also slow is good for logging in and password stuff.
Update 1
@ Gumbo -> No MD5 is not broken, but the main purpose of hashes like MD5 now and in the past was for file checking (as you know to check if the contents of the file can be trusted NOT password storage) as hashes are very quick to decipher since you wouldn't use Bcrypt to check the contents of file since you wait for 30 - 45 seconds... So this means that a hash was specifically meant to be read quickly. Even a SHA512 compared to bcrypt is still inferior completely. Thats why it is imperative to push for strong password algorithms like Blowfish/Bcrypt in PHP. We as users and programmers alike must expand the knowledge that plain password storing or low level hashing algorithms ARE NOT the answer - and should never be used for such sensitive information.
Now to the OP to make your transition over to the new system you would first send a notification to all users stating that "For your security the password encryption system has been updated ........", then you would ask them for a one time password update, once you do the password update you would use the the function called password_verify and if you want to have maximum control of your cost ratio's you use password_needs_rehash if for some reason you choose to change the cost associated with the passwords.
This won't take a massive block of code to do this as the gains you would receive in pass word protection out weigh the negatives of having to "recode" the new password system.
Hopefully this answers most questions, but none the less IRCmaxell's answer is far superior further going into detail on different algorithms! Best of luck OP, and a big thanks to ircmaxell!
Update 2
Also, would a password stored like this be actually crackable? how? (I'm getting curious now)
Any thing and everything is broken is what my Network security professor tells me.. I laughed at him. Now that I see things in his eyes... well yes.. that is absolutely true!
Bcrypt CURRENTLY is the best method of storing passwords! However if you look into Scrypt that seems to be promising but not supported by PHP. None the less any thing and everything is broken, its just a matter of time until some "geek" in a basement will crack Bcrypt. But for now we are safe.. just like we are safe with IPv4 never running out.... oh wait?... ;)
Hopefully this answers your issue that you brought up sir. Also just to put it into context my system is at a cost of 17 and it takes ~20 - ~30 seconds for logging in, but when I presented the system to my professor and my clients and why it should be mandatory they loved it and felt reassured that they were protected.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With