MyBB Community Forums

Full Version: Bcrypt Database Passwords
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
Their have been no tutorials on this for some reason, but today is the day. Mybb uses MD5 for storing passwords and said they are not going to move to Bcrypt until 2.0. I don't think it's a good decision, if someone gets access you your database, they can easily decrypted the hashes (hashcat) to get passwords. Anyway, once you have finished the tutorial, everything is automatic, your users passwords will be converted on login. This may work for older & newer versions of mybb, but you will have to pick through the code (and not go by line numbers).


Notes
1. You will have to make core edits
2. You will have to re-make core edits after major Mybb updates
3. You need a minimum of PHP 5.5.0
4. If you would rather a plugin, you can use This


Before you go any further!
MAKE A BACKUP OF YOUR DATABASE AND THE FILES YOU EDIT!
It's better to be safe than sorry.


First thing we want to do is see if you can use Bcrypt.
Open a new text document, Copy & Paste the below into it, save as cost.php, upload to your server and access it in a browser. If you have the minimum PHP version, It will spit out a number after a few seconds. Copy it, we will be using it later, you can then delete cost.php. If you do not have the minimum php version, you will have to update to 5.5.0 (or newer) and re-run cost.php.
<?php
/**
 * This code will benchmark your server to determine how high of a cost you can
 * afford. You want to set the highest cost that you can without slowing down
 * you server too much. 8-10 is a good baseline, and more is good if your servers
 * are fast enough. The code below aims for ≤ 50 milliseconds stretching time,
 * which is a good baseline for systems handling interactive logins.
 */
if (PHP_VERSION_ID >= 50500) {
	$timeTarget = 0.05; // 50 milliseconds 

	$cost = 8;
	do {
	    $cost++;
	    $start = microtime(true);
	    password_hash("test", PASSWORD_BCRYPT, ["cost" => $cost]);
	    $end = microtime(true);
	} while (($end - $start) < $timeTarget);

	echo "Appropriate Cost Found: " . $cost;
}
else
{
	echo nl2br("Minimum requirement not met!\nYour PHP version: " . PHP_VERSION . "\n PHP version needed: 5.5.0");
}


Edit: \inc\functions_user.php
Line: 73 - 189
Select the following:
/**
 * Checks a password with a supplied uid.
 *
 * @param int $uid The user id.
 * @param string $password The plain-text password.
 * @param array $user An optional user data array.
 * @return boolean|array False when not valid, user data array when valid.
 */
function validate_password_from_uid($uid, $password, $user = array())
{
	global $db, $mybb;
	if(isset($mybb->user['uid']) && $mybb->user['uid'] == $uid)
	{
		$user = $mybb->user;
	}
	if(!$user['password'])
	{
		$query = $db->simple_select("users", "uid,username,password,salt,loginkey,usergroup", "uid='".(int)$uid."'");
		$user = $db->fetch_array($query);
	}
	if(!$user['salt'])
	{
		// Generate a salt for this user and assume the password stored in db is a plain md5 password
		$user['salt'] = generate_salt();
		$user['password'] = salt_password($user['password'], $user['salt']);
		$sql_array = array(
			"salt" => $user['salt'],
			"password" => $user['password']
		);
		$db->update_query("users", $sql_array, "uid='".$user['uid']."'");
	}

	if(!$user['loginkey'])
	{
		$user['loginkey'] = generate_loginkey();
		$sql_array = array(
			"loginkey" => $user['loginkey']
		);
		$db->update_query("users", $sql_array, "uid = ".$user['uid']);
	}
	if(salt_password(md5($password), $user['salt']) === $user['password'])
	{
		return $user;
	}
	else
	{
		return false;
	}
}

/**
 * Updates a user's password.
 *
 * @param int $uid The user's id.
 * @param string $password The md5()'ed password.
 * @param string $salt (Optional) The salt of the user.
 * @return array The new password.
 */
function update_password($uid, $password, $salt="")
{
	global $db, $plugins;

	$newpassword = array();

	// If no salt was specified, check in database first, if still doesn't exist, create one
	if(!$salt)
	{
		$query = $db->simple_select("users", "salt", "uid='$uid'");
		$user = $db->fetch_array($query);
		if($user['salt'])
		{
			$salt = $user['salt'];
		}
		else
		{
			$salt = generate_salt();
		}
		$newpassword['salt'] = $salt;
	}

	// Create new password based on salt
	$saltedpw = salt_password($password, $salt);

	// Generate new login key
	$loginkey = generate_loginkey();

	// Update password and login key in database
	$newpassword['password'] = $saltedpw;
	$newpassword['loginkey'] = $loginkey;
	$db->update_query("users", $newpassword, "uid='$uid'");

	$plugins->run_hooks("password_changed");

	return $newpassword;
}

/**
 * Salts a password based on a supplied salt.
 *
 * @param string $password The md5()'ed password.
 * @param string $salt The salt.
 * @return string The password hash.
 */
function salt_password($password, $salt)
{
	return md5(md5($salt).$password);
}

/**
 * Generates a random salt
 *
 * @return string The salt.
 */
function generate_salt()
{
	return random_str(8);
}
Replace with:
/**
 * Checks a password with a supplied uid.
 *
 * @param int $uid The user id.
 * @param string $password the md5()'ed password.
 * @param array $user An optional user data array.
 * @return boolean|array False when not valid, user data array when valid.
 */
function validate_password_from_uid($uid, $password, $user = array())
{
	global $db, $mybb;
	if(isset($mybb->user['uid']) && $mybb->user['uid'] == $uid)
	{
		$user = $mybb->user;
	}
	if(!$user['password'])
	{
		$query = $db->simple_select("users", "uid,username,password,salt,loginkey,usergroup", "uid='".(int)$uid."'");
		$user = $db->fetch_array($query);
	}
	if(!$user['loginkey'])
	{
		$user['loginkey'] = generate_loginkey();
		$sql_array = array(
			"loginkey" => $user['loginkey']
		);
		$db->update_query("users", $sql_array, "uid = ".$user['uid']);
	}

	if(strlen($user['password']) === 32)
	{
		if(!$user['salt'])
		{
			if ($password !== $user['password'])
				return false;
		}
		else
		{
			if (md5(md5($user['salt']).$password) !== $user['password'])
				return false;
		}

		$user['password'] = salt_password($password);

		$sql_array = array(
			"salt" => Bcrypt,
			"password" => $user['password']
		);
		$db->update_query("users", $sql_array, "uid='".$user['uid']."'");

		return $user;
	}
	if(password_verify($password, $user['password']) || password_verify(md5($password), $user['password'])) // UserCp does not use MD5, add MD5 to password_verify for changing password
	{
		return $user;
	}
	else
	{
		return false;
	}
	return false;
}

/**
 * Updates a user's password.
 *
 * @param int $uid The user's id.
 * @param string $password the md5()'ed password.
 * @param string $salt (Optional) The salt of the user.
 * @return array The new password.
 */
function update_password($uid, $password, $salt="")
{
	global $db, $plugins;

	// Create new password
	$user['password'] = salt_password($password);

	// Generate new login key
	$user['loginkey'] = generate_loginkey();

	$sql_array = array(
		"salt" => Bcrypt,
		"password" => $user['password'],
		"loginkey" => $user['loginkey']
	);
	$db->update_query("users", $sql_array, "uid='$uid'");

	$plugins->run_hooks("password_changed");

	return $user;
}

/**
 * Bcrypt password.
 *
 * @param string $password the md5()'ed password.
 * @return string The password hash.
 */
function salt_password($password)
{
	$options = array('cost' => 11);
	return password_hash($password, PASSWORD_BCRYPT, $options);
}

/**
 * Dummy Bcrypt salt
 *
 * @return string The salt.
 */
function generate_salt()
{
	return 'Bcrypt';
}


Edit: \inc\functions_user.php
Line: 173
Find the following and change the cost (default 11) to what cost.php gave you.
$options = array('cost' => 11);


Edit: \inc\datahandlers\login.php
Line: 174 - 206
Select the following:
	
	if($strict == true)
		{
			if(!$this->login_data['salt'])
			{
				// Generate a salt for this user and assume the password stored in db is a plain md5 password
				$this->login_data['salt'] = generate_salt();
				$this->login_data['password'] = salt_password($this->login_data['password'], $this->login_data['salt']);

				$sql_array = array(
					"salt" => $this->login_data['salt'],
					"password" => $this->login_data['password']
				);

				$db->update_query("users", $sql_array, "uid = '{$this->login_data['uid']}'");
			}

			if(!$this->login_data['loginkey'])
			{
				$this->login_data['loginkey'] = generate_loginkey();

				$sql_array = array(
					"loginkey" => $this->login_data['loginkey']
				);

				$db->update_query("users", $sql_array, "uid = '{$this->login_data['uid']}'");
			}
		}

		$salted_password = md5(md5($this->login_data['salt']).$password);

		$plugins->run_hooks('datahandler_login_verify_password_end', $args);

		if($salted_password !== $this->login_data['password'])
Replace with:
		$plugins->run_hooks('datahandler_login_verify_password_end', $args);

		if(validate_password_from_username($this->login_data['username'], $password) === false)
Quote:Their have been no tutorials on this for some reason

I released a tutorial for this elsewhere, over six months ago.
I did not find anything in cost.php

[Image: 2MwdaDn.png]
You must create cost.php and copy and paste what I have in the code box.

(2016-03-11, 02:39 AM)Paradise Wrote: [ -> ]I did not find anything in cost.php
(2016-03-11, 03:41 AM)Boonie Wrote: [ -> ]You must create cost.php and copy and paste what I have in the code box.

(2016-03-11, 02:39 AM)Paradise Wrote: [ -> ]I did not find anything in cost.php

I've copy and paste the code as well as upload and the result is not found when I access www.forum.com/cost.php.
Is your server using PHP 5.5?
nice feature for a small community and on php 5.5 > servers. other wise it needs the package from github to work. also it can cause issues with other plugins to as some plugins need certain things from the login to work.

bcrypt is a nice feature but it doesn't fully protect the website.
(2016-09-17, 05:52 AM)leroymcqy Wrote: [ -> ]bcrypt is a nice feature but it doesn't fully protect the website.

Neither does MD5. It's not meant to. Security is a process, not a product. Switching from MD5 to bcrypt will make cracking the hashes of your users significantly more difficult. That's an important part of the security process. It's a defensive layer post-compromise.
(2016-09-17, 07:23 AM)Nathan Malcolm Wrote: [ -> ]
(2016-09-17, 05:52 AM)leroymcqy Wrote: [ -> ]bcrypt is a nice feature but it doesn't fully protect the website.

Neither does MD5. It's not meant to. Security is a process, not a product. Switching from MD5 to bcrypt will make cracking the hashes of your users significantly more difficult. That's an important part of the security process. It's a defensive layer post-compromise.

yeah i know. but not all hosts supports it tho. which is why before anyone to install it or try it check to make sure its able to be used. or you gonna have to search up the password_hash on github and add it as a class reference.

its on php 5.5 or higher last i checked. i know for sure on php 5.6 or higher.
Pages: 1 2