Добавляем хеширование паролей

Хранить пароли в открытом виде в базе данных весьма опасно. Злоумышленник может получить доступ к вашей базе и украсть все логины пользователей вместе с их паролями!

Не думайте, что получить доступ к базе очень тяжело и этого никогда не случится - достаточно удачно провести SQL-инъекцию и злоумышленник получит данные из вашей базы данных.

Чтобы этого избежать, на пути хакера следует поставить еще одну защиту: хеширование паролей.

В том случае, если ему все-таки удастся попасть в базу данных - он вместо паролей получит их хеши и не сможет их расшифровать (так как шифрование необратимое)!

Если вы уже забыли о том, что это такое или вообще не прочитали об этом - смотрите урок "Авторизация через файлы".

Итак, давайте вспомним, как выглядела раньше таблица users:

id login (Логин) password (Пароль)
1 user 12345
2 admin 54321

Добавим теперь вместо паролей их хеши md5():

id login (Логин) password (Пароль)
1 user 827ccb0eea8a706c4c34a16891f84e7b
2 admin 01cfcd4f6b8770febfb40cb906715822

Вы можете сами убедиться, глядя на второй вариант таблицы, что хакеру она ничего не скажет!

Теперь нужно доработать код авторизации и регистрации с учетом хеширования. Изменение в коде регистрации будут только тут:

<?php
	$query = 'INSERT INTO users SET login="'.$login.'", password="'.md5($password).'"';
?>

А в авторизации - тут:

<?php
	$query = 'SELECT*FROM users WHERE login="'.$login.'" AND password="'.md5($password).'"';
?>

Добавляем соль

На самом деле md5 не дает полной защиты от расшифровки.

В случае простого или очень популярного пароля хеш элементарно расшифровывается с помощью гугла.

Проверьте это сами: пароль для пользователя user был '12345', а его хеш md5 – '827ccb0eea8a706c4c34a16891f84e7b'. Вбейте эту строку в гугл и вы сразу получите расшифровку пароля!

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

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

Это серьезная проблема для защиты.

Одним из выходом является изменение алгоритма шифрования. Например, вместо md5 вводится md5(md5($password)). Однако это тоже не выход - в последнее время начали появляться таблицы паролей не только для md5, но и для дважды md5 и даже трижды.

Проблема в том, что пользователи имеют тенденцию задавать простые пароли и с этим в принципе ничего не сделать.

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

Строка добавляется при регистрации до того, как от пароля взят md5.

Пусть $salt - это случайно сформированная строка (наша соль), а $password - пароль. Тогда при регистрации делается так: md5($password.$salt) и это значение (соленый пароль) заносится в базу на место пароля.

Теперь даже если пароль пользователя '12345', то в базе он будет хранится уже соленым и хакер не сможет расшифровать этот пароль ни с помощью гугла, ни с помощью радужных таблиц!

Как сделать соль?

Соль должна представлять собой случайную строку, обычно из 8 символов. Случайный символ получается с помощью конструкции chr(mt_rand(33,126)).

Вот готовая функция для генерации соли:

<?php
	function generateSalt()
	{
		$salt = '';
		$saltLength = 8; //длина соли
		for($i=0; $i<$saltLength; $i++) {
			$salt .= chr(mt_rand(33,126)); //символ из ASCII-table
		}
		return $salt;
	}
?>

Соль должна быть уникальная для каждого пользователя.

Соль следует хранить в базе данных, причем в открытом виде, так как она нам понадобится при авторизации.

Пароль, естественно, мы храним в виде хеша (пароль+соль).

Добавим в таблицу users соль (пароли уже посолены md5($password.$salt)):

id login (Логин) password (Соленый пароль) salt (Соль)
1 user 827ccb0eea8a706c4c34a16891f84e7b sdfLjgyl
2 admin 01cfcd4f6b8770febfb40cb906715822 sMtrnwpJ

Различные варианты соления в известных движках:

  • Соление md5($password.$salt) применяется в Joomla.
  • Соление md5(md5($password).$salt) применяется в vBulletin.
  • Соление md5(md5($salt).md5($password)) применяется в IP.Board.

Изменим код регистрации с учетом соли:

<!--  Здесь форма регистрации. -->

<?php
	//Если форма регистрации отправлена и ВСЕ поля непустые...
	if (
		!empty($_REQUEST['password'])
		and !empty($_REQUEST['password_confirm'])
		and !empty($_REQUEST['login'])
	) {
		//Пишем логин и пароль из формы в переменные (для удобства работы):
		$login = $_REQUEST['login']; 
		$password = $_REQUEST['password']; 
		$password_confirm = $_REQUEST['password_confirm']; //подтверждение пароля

		//Если пароль и его подтверждение совпадают...
		if ($password == $password_confirm) {
			/*
				Выполняем проверку на незанятость логина.
				Ответ базы запишем в переменную $isLoginFree. 

				ВЫБРАТЬ ИЗ таблицы_users ГДЕ логин = $login.
			*/
			$query = 'SELECT*FROM users WHERE login="'.$login.'"';
			$isLoginFree = mysqli_fetch_assoc(mysqli_query($link, $query));

			//Если $isLoginFree пустой - то логин не занят!
			if (empty($isLoginFree)) {
				//Генерируем соль с помощью функции generateSalt() и солим пароль...
				$salt = generateSalt(); //генерируем соль
				$saltedPassword = md5($password.$salt); //соленый пароль

				/*
					Формируем и отсылаем SQL запрос:

					ВСТАВИТЬ В таблицу_users УСТАНОВИТЬ 
					логин = $login, пароль = $saltedPassword, salt = $salt
				*/
				$query = 'INSERT INTO users SET login="'.$login.'", 
					password="'.$saltedPassword.'", salt="'.$salt.'"';
				mysqli_query($link, $query); 

				//Выведем сообщение об успешной регистрации:
				echo 'Вы успешно зарегистрированы!';
			}
			//Если $isLoginFree НЕ пустой - то логин занят!
			else {
				echo 'Такой логин уже занят!';
			}
		}
		//Если пароль и его подтверждение НЕ совпадают - выведем ошибку:
		else {
			echo 'Пароли не совпадают!';
		}
	}
	//Не заполнено какого-либо из полей...
	else {
		echo 'Поля не могут быть пустыми!';
	}
?>

Поменяем теперь код авторизации.

Обратите внимание на то, что если раньше мы проверяли правильность пары логин-пароль с помощью SQL запроса, то теперь так не получится.

Мы должны найти пользователя с заданным логином с помощью SQL запроса (логин из формы авторизации) и далее в скрипте сравнить соленый пароль из базы с паролем из формы (его нужно будет посолить солью из базы):

<!-- Здесь форма авторизации, не написана для простоты -->

<?php
	//Если форма авторизации отправлена...
	if ( !empty($_REQUEST['password']) and !empty($_REQUEST['login']) ) {
		//Пишем логин и пароль из формы в переменные (для удобства работы):
		$login = $_REQUEST['login']; 
		$password = $_REQUEST['password']; 

		/*
			Формируем и отсылаем SQL запрос:
			ВЫБРАТЬ ИЗ таблицы_users ГДЕ поле_логин = $login
		*/
		$query = 'SELECT*FROM users WHERE login="'.$login.'"';
		$result = mysql_query($query); //ответ базы запишем в переменную $result

		//Преобразуем ответ из БД в нормальный массив PHP:
		$user = mysql_fetch_assoc($result); 

		//Если база данных вернула не пустой ответ - значит такой логин есть...
		if (!empty($user)) {
			//Получим соль:
			$salt = $user['salt'];

			//Посолим пароль из формы:
			$saltedPassword = md5($password.$salt);

			//Если соленый пароль из базы совпадает с соленым паролем из формы...
			if ($user['password'] == $saltedPassword) {
				//Стартуем сессию:
				session_start(); 

				//Пишем в сессию информацию о том, что мы авторизовались:
				$_SESSION['auth'] = true; 

				/*
					Пишем в сессию логин и id пользователя
					(их мы берем из переменной $user!):
				*/
				$_SESSION['id'] = $user['id']; 
				$_SESSION['login'] = $user['login']; 
			}
			//Если соленый пароль из базы НЕ совпадает с соленым паролем из формы...
			else {
				//Выводим сообщение 'Неправильный логин или пароль'.
			}
		} else {
			//Нет такого логина, выведем сообщение об ошибке.
		}
	}
?>

Валидация при регистрации

При регистрации обязательно следует проверять данные, которые пользователь ввел в поля.

Логин, например, не должен быть меньше трех-четырех символов, а пароль вообще желательно ограничить минимум 6-ю символами (в целях безопасности).

Если в поле должен быть введен email - это тоже следует проверять (с помощью регулярного выражения). И так далее.

Обратите на это внимание. Я это не показываю, дабы не усложнять примеры, но вам это необходимо будет реализовать в реальных условиях.