К сожалению, применения одной только функции 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
с именами пользователей и паролями bsmith
— mysecret
и pjones
— acrobat
соответственно.
Теперь, используя данные, имеющиеся в этой таблице, мы можем модифицировать код примера «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
и добавление к ней двух учетных записей».
Сохранение имен пользователей и паролей | Использование сессий |