Материал предоставлен https://it.rfei.ru

Добавление произвольных данных

К сожалению, применения одной только функции md5 для защиты базы данных с паролями недостаточно, поскольку пароль можно вскрыть путем перебора, в котором используется другая база данных, состоящая из известных 32-символьных шестнадцатеричных лексем, выдаваемых функцией md5. В существовании подобных баз данных можно убедиться, воспользовавшись поисковой системой Google.

К счастью, любой поисковой атаке можно поставить заслон путем добавления произвольных данных ко всем паролям перед их отправкой функции md5. Этот прием, ассоциирующийся с «посыпанием солью» (salting), заключается в простом добавлении некоего известного только нам текста к любому кодируемому параметру:

$token = md5('Дополнительная_строка_мой_пароль');

В данном примере перед паролем был набран текст «Дополнительная_строка». Конечно, чем малоизвестнее будет эта дополнительная строка, тем лучше. Предпочтительно добавлять строку, похожую на эту:

$token = md5('hqb%$tмой_пapoльcg*l');

Здесь произвольные символы помещены как до, так и после пароля. На основе лишь тех данных, которые хранятся в базе, не имея доступа к вашему РНР-коду, вскрыть хранящиеся пароли практически невозможно.

Для проверки принадлежащего кому-нибудь пароля при входе на сайт нужно будет лишь добавить те же самые произвольные строки в начало и конец пароля, а затем сравнить полученную от функции md5 лексему с той, которая была сохранена в базе данных для данного пользователя.

Давайте создадим таблицу MySQL, в которой будут храниться сведения о пользователе, и добавим к ней пару учетных записей. Наберите код, показанный в следующем примере, и сохраните его в файле setupusers.php, после чего откройте этот файл в своем браузере.

Пример. Создание таблицы users и добавление к ней двух учетных записей

<?php //setupusers.php
require_once 'login.php';
$db_server = mysql_connect($db_hostname, $db_username, $db_password);
if (!$db_server) die("Unable to connect to MySQL: " . mysql_error());
mysql_select_db($db_database)
	or die("Unable to select database: " . mysql_error());

$query = "CREATE TABLE users (
			forename VARCHAR(32) NOT NULL,
			surname  VARCHAR(32) NOT NULL,
			username VARCHAR(32) NOT NULL UNIQUE,
			password VARCHAR(32) NOT NULL
		)";

$result = mysql_query($query);
if (!$result) die ("Database access failed: " . mysql_error());

$salt1 = "qm&h*";
$salt2 = "pg!@";

$forename = 'Bill';
$surname  = 'Smith';
$username = 'bsmith';
$password = 'mysecret';
$token    = md5("$salt1$password$salt2");
add_user($forename, $surname, $username, $token);

$forename = 'Pauline';
$surname  = 'Jones';
$username = 'pjones';
$password = 'acrobat';
$token    = md5("$salt1$password$salt2");
add_user($forename, $surname, $username, $token);

function add_user($fn, $sn, $un, $pw)
{
	$query = "INSERT INTO users VALUES('$fn', '$sn', '$un', '$pw')";
	$result = mysql_query($query);
	if (!$result) die ("Database access failed: " . mysql_error());
}
?>

Эта программа создаст в вашей базе данных publications (или в той базе данных, на которую вы настроились в файле login.php в главе «Доступ к MySQL с использованием РНР») таблицу users. В этой таблице будут созданы два пользователя — Bill Smith и Pauline Jones с именами пользователей и паролями bsmithmysecret и pjonesacrobat соответственно.

Теперь, используя данные, имеющиеся в этой таблице, мы можем модифицировать код примера «PHP-аутентификация с проверкой вводимой информации» для вполне приемлемой аутентификации пользователей. Необходимый для этого код показан в следующем примере. Наберите этот код, сохраните его в файле authenticate.php и вызовите эту программу в своем браузере.

Пример. PHP-аутентификация с использованием MySQL

<?php // authenticate.php
require_once 'login.php';
$db_server = mysql_connect($db_hostname, $db_username, $db_password);
if (!$db_server) die("Unable to connect to MySQL: " . mysql_error());
mysql_select_db($db_database)
	or die("Unable to select database: " . mysql_error());

if (isset($_SERVER['PHP_AUTH_USER']) &&
	isset($_SERVER['PHP_AUTH_PW']))
{
	$un_temp = mysql_entities_fix_string($_SERVER['PHP_AUTH_USER']);
	$pw_temp = mysql_entities_fix_string($_SERVER['PHP_AUTH_PW']);

	$query = "SELECT * FROM users WHERE username='$un_temp'";
	$result = mysql_query($query);
	if (!$result) die("Database access failed: " . mysql_error());
	elseif (mysql_num_rows($result))
	{
		$row = mysql_fetch_row($result);
		$salt1 = "qm&h*";
		$salt2 = "pg!@";
		$token = md5("$salt1$pw_temp$salt2");

		if ($token == $row[3]) echo "$row[0] $row[1] :
			Hi $row[0], you are now logged in as '$row[2]'";
		else die("Invalid username/password combination");
	}
	else die("Invalid username/password combination");
}
else
{
	header('WWW-Authenticate: Basic realm="Restricted Section"');
	header('HTTP/1.0 401 Unauthorized');
	die ("Please enter your username and password");
}

function mysql_entities_fix_string($string)
{
	return htmlentities(mysql_fix_string($string));
}

function mysql_fix_string($string)
{
	if (get_magic_quotes_gpc()) $string = stripslashes($string);
	return mysql_real_escape_string($string);
}
?>

Вы, наверное, ожидали, что настало время для некоторых весьма больших по объему примеров кода. Но по этому поводу не стоит переживать. Последние 10 строк — не что иное, как пример, который уже встречался в главе «Доступ к MySQL с использованием РНР». Эти строки присутствуют здесь для выполнения очень важной задачи — обезвреживания введенных пользователем данных.

На данный момент практический интерес для вас должны представлять только те строки, которые выделены жирным шрифтом и начинаются с присваивания значений двум переменным — $un_temp и $pw_temp с использованием отправленных имени пользователя и пароля. Затем выдается запрос к MySQL на поиск пользователя с именем $un_temp, и, если будет возвращен результат, значение его первой строки присваивается переменной $row. (Поскольку имя пользователя уникально, возвращена будет только одна строка.) Потом создаются переменные $salt1 и $salt2 с двумя произвольными строками, которые затем добавляются до и после отправленного пароля $pw_temp. После этого получившаяся в результате строка передается функции md5, которая возвращает 32-символьное шестнадцатеричное значение, присваиваемое переменной $token.

Теперь остается лишь сравнить значение переменной $token со значением, хранящимся в базе данных в четвертой графе, которая при начале отсчета с нуля соответствует графе 3. То есть $row[3] содержит предыдущую лексему, вычисленную для «посыпанного солью» пароля. Если значения совпадают, выдается строка приветствия, в которой содержится обращение к пользователю по его настоящему имени (см. рис.). В противном случае выдается сообщение об ошибке. Как уже упоминалось, это сообщение всегда содержит одну и ту же информацию независимо от того, существует такое имя пользователя или нет, поскольку потенциальный взломщик или тот, кто подбирает пароли, получает в результате этого минимум полезной для себя информации.

Вы можете самостоятельно испытать эту программу в работе, вызвав ее в браузере и набрав имя пользователя bsmith и пароль mysecret (или набрав пару pjones и acrobat), то есть те значения, которые были сохранены в базе данных программой из примера «Создание таблицы users и добавление к ней двух учетных записей».

Аутентификация пользователя Bill Smith прошла успешно

Сохранение имен пользователей и паролейИспользование сессий