Jump to content

Advanced password hashing improvements


phpzer

Recommended Posts

Hello,

I don't know if it's the right section.

I wrote an advanced password hashing system with support of many salts (encrypted using AES) and count on how many times the password should be hashes. 

I would like to hear your opinion about this and maybe you can give me some ideas on how to improve it, make it more safe or maybe add some kind of function.

PS: If you like the class you are free to use it in your projects.

<?php

/**
 * Password class.
 *
 * Class for hashing passwords in a safe way.
 *
 * @version 1.2
 */
class password {

	/**
	 * The encryption system to use in place of sha2
	 * @var string
	 */
	private $sha2;
	
	/**
	 * The encryption system to use in place of md5
	 * @var string
	 */
	private $md5;

	/**
	 * The hash function hashes a password in the most safe way.
	 * @param string $pass, the password you want to hash
	 * @param array[string] $saltArray, an array of strings of the salts you want to use
	 * @param (optional) int $times, the times your password must be hashed
	 * @return string, the hashed password.
	 */
	function hash($pass, $saltArray, $times = 5) {
		if(is_string($saltArray))
			$saltArray = [$saltArray];
		$password = implode(chr($times), str_split($pass));
		/* SHA1 has been cracked so it's NOT SAFE, same thing for MD5. MD2 is OFF-LIMITS as it can be cracked with timing-attack */
		$this->sha2 = $this->___chooseBest("sha512", "sha384", "sha256", "ripemd320", "ripmed256", "gost");
		$this->md5 =  $this->___chooseBest("whirlpool", "haval256,5", "haval256,3", "snefru", "gost");
		$saltArray[] = chr(0).__FILE__;
		foreach($saltArray as $salt) {
			$password = strrev($password);
			$password = $this->__encrypt($password, $salt, $times);
			$password = base64_encode($password);
		}
		return $password;
	}
	
	/**
	 * The function __encrypt is an internal function to encrypt a password with the given hash and the given salt
	 * @param string $password, the password to crypt
	 * @param string $salt, the salt to use
	 * @param int $times, how many tiems should the encryption be done?
	 */
	function __encrypt($password, $salt, $times) {
		$salt = chr($times).$salt.chr($times);
		$salt = hash($this->sha2, $salt);
		$salt = base64_encode($salt);
		$salt = $this->split_str($salt);
		for($i = 0; $i < $times; $i++) {
			$passord = hash($this->md5, $salt[0].$password.$salt[1]);
			$password = implode(chr($times), str_split($password));
			$password = hash($this->sha2, $salt[1].$password.$salt[0]);
		}
		return $password;
	}
	
	/**
	 * The fucntion __choseBest is an internal function to choose the best hash function from a given list
	 * @param string [...] All the hash functions you want to check for. First = best, last = worst
	 * @return (bool|string), returns the best hash algo or false in case of failure
	 */
	function ___chooseBest() {
		$algos = hash_algos();
		$functions = func_get_args();
		foreach($functions as $f)
			if(in_array($f, $algos))
				return $f;
		return false;
	}
	
	/**
	 * Use this function to generate n CSPRNG salts 
	 * @param int $count, how many Salts should be generated?
	 * @param int $len, the length of the generated salts
	 * @return array[string], the generated salts
	 */
	function generateSalt($count = 5, $len = 32) {
		$salts = [];
		for($i = 0; $i < $count; $i++)
			$salts[] = bin2hex(openssl_random_pseudo_bytes($len));
		return $salts;
	}

	/**
	 * The function split_str is an internal function for splitting a string in half
	 * @param string $string, the string to be splitte in half
	 * @return array[string], the two parts of the string
	 */
	function split_str($string) {
		$len = ceil(strlen($string) / 2);
		$ret = [substr($string, 0, $len), substr($string, $len)];
		return $ret;
	}
	
	/**
	 * The function decodeSalts is used for decoding the salts
	 * The salts are encrypted using Advanced Encryption System (AES).
	 * The password should be different for all the users
	 * @param string $salts, the encoded salts loaded from the database
	 * @param string $password, the password for those salts
	 * @return array[string], the decoded salts
	 */
	function decodeSalts($salts, $password) {
		if(strlen($password) != 32)
			$password = hash("sha256", $password, true);
		$salts = base64_decode($salts);
		$salts = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $password, $salts, MCRYPT_MODE_CBC, str_repeat("\0", 16));
		$padding = ord($salts[strlen($salts) -1]);
		$salts = substr($salts, 0, -$padding);
		return json_decode($salts);
	}
	
	/**
	 * This function is used for encoding the salts
	 * The salts are encoded using Advanced Encryption System (AES).
	 * The password should be different for all the users
	 * @param array[string] $salts, the strings that must be encoded
	 * @param string $password, the password for encoding those salts
	 * @return string, the encrypted salts
	 */
	function encodeSalts($salts, $password) {
		if(strlen($password) != 32) // 32 = 256 bit
			$password = hash($this->sha2, $password, true);
		$salts = json_encode($salts);
		$padding = 16 - (strlen($salts) % 16);
		$salts .= str_repeat(chr($padding), $padding);
		return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $password, $salts, MCRYPT_MODE_CBC, str_repeat("\0", 16)));
	}
	
	
	
	/**
	 * The function string_check checks if two strings are the same in a way that prevents timing attacks
	 * @param string $a, the first string 
	 * @param string $b, the second string
	 * @return int, the differences beetween the string, check with === 0 for seeing if the passwords are the same
	 */
	function string_check($a, $b) {
		$diff = strlen($a) ^ strlen($b);
		for($i = 0; $i < strlen($a) && $i < strlen($b); $i++)
			$diff |= ord($a[$i]) ^ ord($b[$i]);
		return $diff;
	}
}
Edited by phpzer
Link to comment
Share on other sites

You're missing the whole point of password hashing, phpzer. You do a lot of hashing and encrypting and encoding and whatnot, but you don't seem to understand why you're doing it. This is just an arbitrary collection of pseudo-cryptographic voodoo.

 

The biggest threat for password hashes is a brute-force attack. To withstand those attacks, a hash algorithm has to be computationally expensive. That's the only counter-measure: To slow down the attacker so that it's impractical for them to break anything but the weakest passwords.

 

Your algorithm isn't expensive at all. An average gamer PC can calculate billions(!) of SHA-2 hashes per second. Sure, you repeat the hashing a few times. But that only means an attacker might need, say, 2 minutes instead of 1 to break an average password. Great. Actually, how can you be so sure that your nested hashes and string magic don't introduce cryptographic weaknesses? Don't tell me you've written a mathematical proof for each function call.

 

Encrypting the salt is also nonsensical. First of all, that's not what it's meant for. The purpose of the salt is to act as additional input so that the same password doesn't lead to the same hash. Secondly, if an attacker has already managed to obtain the password hashes, it's pretty naïve to assume that the salt is still perfectly secure. I mean, it's readable by the very server that has just been compromised.

Long story short, this is in no way better than plain old MD5. It's actually much worse, because MD5 was intensively researched, and we know its strengths and weaknesses. Your stuff hasn't been researched by anybody. The only person who thinks it's good is: you.

 

Link to comment
Share on other sites

@Jacques1 That's the answer I was expecting, something that explains why it's good and why it's not. 

 

You're missing the whole point of password hashing, phpzer. You do a lot of hashing and encrypting and encoding and whatnot, but you don't seem to understand why you're doing it. This is just an arbitrary collection of pseudo-cryptographic voodoo.

 

The biggest threat for password hashes is a brute-force attack. To withstand those attacks, a hash algorithm has to be computationally expensive. That's the only counter-measure: To slow down the attacker so that it's impractical for them to break anything but the weakest passwords.

 

Sure, that's why I built the computation cost (variable $times). A brute force would be difficult to be done, the hacker must know my hashing class.

 

Your algorithm isn't expensive at all. An average gamer PC can calculate billions(!) of SHA-2 hashes per second. Sure, you repeat the hashing a few times. But that only means an attacker might need, say, 2 minutes instead of 1 to break an average password.

 

Yes, and? He can even have the world's best supercomputer that hashes all the combinations of 256 characters passwords in half a second (I doubt something like that exists) but if he doesn't know what part of the string he got is the actual user password, then he did nothing!!.

 

Great. Actually, how can you be so sure that your nested hashes and string magic don't introduce cryptographic weaknesses? Don't tell me you've written a mathematical proof for each function call.

Good point... To be honest I didn't really think about that...

 

Encrypting the salt is also nonsensical. First of all, that's not what it's meant for. The purpose of the salt is to act as additional input so that the same password doesn't lead to the same hash. Secondly, if an attacker has already managed to obtain the password hashes, it's pretty naïve to assume that the salt is still perfectly secure. I mean, it's readable by the very server that has just been compromised.

It's AES encrypted, it's unbreakable with the computers we have right now.

 

 

Long story short, this is in no way better than plain old MD5. It's actually much worse, because MD5 was intensively researched, and we know its strengths and weaknesses. Your stuff hasn't been researched by anybody.

You know, it has been quite a while since MD5 was cracked... you should probably NOT use it.
Link to comment
Share on other sites

Sure, that's why I built the computation cost (variable $times). A brute force would be difficult to be done, the hacker must know my hashing class.

 

You mean the class you've just published on the Internet? ::)

 

Anyway, this is security through obscurity, a very common fallacy. People try it again and again, and it fails again and again. An algorithm that is only secure as long as it's kept secret isn't secure at all.

 

A cost factor alone doesn't mean much. This is where it gets into the ugly details of cryptography, and I think none of us is qualified to talk about that.

 

 

 

 

Yes, and? He can even have the world's best supercomputer that hashes all the combinations of 256 characters passwords in half a second (I doubt something like that exists) but if he doesn't know what part of the string he got is the actual user password, then he did nothing!!.

 

Again, security through obscurity.

 

 

 

 

It's AES encrypted, it's unbreakable with the computers we have right now.

Very funny.

 

AES is no magic spell to make systems secure. The server that manages the passwords needs access to the plaintext salts. That means you either have to store the AES keys right next to the encrypted salts, which would make the whole thing pretty silly. Or the authentication server needs to be able to fetch the plaintext salts from some external server, in which case the AES encryption is irrelevant for the attacker.

 

Either way, the encryption doesn't really do anything. I mean, if you know how to build perfectly secure systems, why don't you simply protect the hashes?

 

 

 

 

You know, it has been quite a while since MD5 was cracked... you should probably NOT use it.

 

Nonsense. Sorry, but I have no idea why people keep perpetuating this myth.

 

SHA-2, SHA-3 or whatever are in no way better for hashing passwords than MD5. They're all equally bad. The problem is that they were designed for digital signatures, not for password hashes. They're supposed to be very fast and efficient, which is the last thing you want when hashing a password.

 

bcrypt, on the other hand, was specifically designed to be slow and expensive. Reality has proven that it's able to withstand brute-force attacks as long as the password is relatively strong. This is the de-facto standard for hashing passwords. And since PHP 5.5 got the new bcrypt API (see trq's post), there's really no excuse for not using it.

Link to comment
Share on other sites

For hashing, I've started using the php password script that's mentioned by trq. http://us2.php.net/password The biggest benifit is that the hash will update with the PHP versions. No reason to reinvent the wheel.

if( password_verify( $password, $hashedpassword ) ) {
  // user logged in
  if( password_needs_rehash( $hashedpassword, PASSWORD_DEFAULT ) ) {
    $updatedhash = password_hash( $password, PASSWORD_DEFAULT );
    // insert updated hash into database
  }
}

The only problem is that it wasn't introduced til PHP 5.5.

Link to comment
Share on other sites

You mean the class you've just published on the Internet? ::)

:) Ok this was an epic fail

 

A cost factor alone doesn't mean much. This is where it gets into the ugly details of cryptography, and I think none of us is qualified to talk about that.

Of course, I never said I'm qualified in cryptography or anything!

 

Very funny.

 

AES is no magic spell to make systems secure. The server that manages the passwords needs access to the plaintext salts. That means you either have to store the AES keys right next to the encrypted salts, which would make the whole thing pretty silly. Or the authentication server needs to be able to fetch the plaintext salts from some external server, in which case the AES encryption is irrelevant for the attacker.

I know AES is not magic or anything. My idea of keeping the password (for AES) was some kind of calculated password that would have been changed every time a user logs in. The password would not be stored in plaintext, it would be encrypted with some kind of "master password" (there are hardware things that are done just for holding password, I forgot how they're named) but the comes what you told me, security through obscurity.

 

Either way, the encryption doesn't really do anything. I mean, if you know how to build perfectly secure systems, why don't you simply protect the hashes?

Even if I would be able to build an unbreakable PHP code, some hacker would still be able to get through the system somehow, for example through a bug in Apache (or nginx or whatever I will use as webserver) or even a bug in the linux kernel. I mean there is no 100% secure system.

 

PS: sorry if this post contains grammar errors or isn't understandable or is unredable but it's 3AM here and I'm falling asleep :)

Link to comment
Share on other sites

Of course, I never said I'm qualified in cryptography or anything!

 

I know. What I mean is that we should leave cryptography to the people who actually understand it.

 

Designing a hash algorithm is definitely an expert topic. And the result needs to be reviewed and tested by many people over a long period of time. If the algorithm has survived that, then it's ready for use.

 

 

 

Even if I would be able to build an unbreakable PHP code, some hacker would still be able to get through the system somehow, for example through a bug in Apache (or nginx or whatever I will use as webserver) or even a bug in the linux kernel. I mean there is no 100% secure system.

 

Right. That's why it's a good idea to expect the worst and make as few assumptions as possible. Password hashes should be secure even if the entire server has been compromised. They shouldn't depend on some secret data which might be stolen (be it a key, the salt, the algorithm itself or whatever).

 

 

 

PS: sorry if this post contains grammar errors or isn't understandable or is unredable but it's 3AM here and I'm falling asleep :)

 

So am I. :happy-04:

 

By the way, kudos for being open to critique. :)

Link to comment
Share on other sites

I know. What I mean is that we should leave cryptography to the people who actually understand it.

 

Designing a hash algorithm is definitely an expert topic. And the result needs to be reviewed and tested by many people over a long period of time. If the algorithm has survived that, then it's ready for use.

I think you are right. I thought that maybe doing all that encryption and encoding and string editing then it would be safe enough but now I think I was wrong...

 

Right. That's why it's a good idea to expect the worst and make as few assumptions as possible. Password hashes should be secure even if the entire server has been compromised. They shouldn't depend on some secret data which might be stolen (be it a key, the salt, the algorithm itself or whatever).

Sure

 

 

By the way, kudos for being open to critique. :)

I am always open to constructive critique ;)

 

For hashing, I've started using the php password script that's mentioned by trq. http://us2.php.net/password The biggest benifit is that the hash will update with the PHP versions. No reason to reinvent the wheel.

if( password_verify( $password, $hashedpassword ) ) {
  // user logged in
  if( password_needs_rehash( $hashedpassword, PASSWORD_DEFAULT ) ) {
    $updatedhash = password_hash( $password, PASSWORD_DEFAULT );
    // insert updated hash into database
  }
}
The only problem is that it wasn't introduced til PHP 5.5.

 

Will take a look at it, thank you

Edited by phpzer
Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.