Простые методы для повышения уровня безопасности
Одним из критических пунктов практики разработки PHP приложений является постоянное удерживание в памяти важности вопросов безопасности, что совсем не так просто. Чтобы убедиться в том, что безопасность вашим вэб приложений включена в рабочий процесс, ее нужно постоянно оценивать, отслеживать и усилять.
Введение
До тех пор, пока использование фильтров и проверок данных является частью процессов безопасности, вэб разработчику нужно помнить о том, что криптография, случайные числа и запутывание кода могут существенно изменить уровень безопасности вэб приложения. Данная статья описывает несколько простых методов создания и использования случайных или уникальных значений в ваших вэб приложениях, проводит обзор использования некоторых техник запутывания кода, а также более глубоко описывает науку криптографии и ее использование в PHP.
Что Вы узнаете в данной статье
Как генерировать случайные числа в PHP
Генерация случайных паролей
Хранение паролей и аутентификация пользователей
Обзор запутывания кода в PHP
Криптография в PHP и ее применение
Генерирование случайных чисел
Генерация случайных чисел определяется несколькими способами, однако вычислительные генераторы не достигают настоящей случайности, такой как белый шум (хороший пример белого шума - отсутствие настройки в чернобелом телевизоре, который в такие моменты показывает шипящий и мерцающий экран). Вычисленные числа называются псевдо-случайными.
В PHP существует два различных метода для получения случайного значения. Рассмотрим несколько наиболее популярных функций.
<?php
rand(int $min, int $max);
mt_rand(int $min, int $max);
str_shuffle($str);
uniqid($prefix, more_entropy=);
?>
Две функции rand() и mt_rand() наиболее часто используемые функции для генерации случайных чисел в PHP. Функция rand(), более старая версия генератора, вытесняется mt_rand(), которая быстрее, более надежная и может работать с максимально большим значением типа integer на большинстве платформ. Функция str_shuffle() не совсем точно отражает сущность генератора случайных чисел. Она смешивает строку, которую получает в качестве аргумента.
<?php
//Пример использования mt_rand()
print mt_rand();//По умолчанию
echo "<br />";
print mt_rand(0, 20);//Выводит случайное целое между 0 и 20
echo "<br />";
//Примеры использования rand()
print rand();//По умолчанию
echo "<br />";
print rand(0, 25);//Выводит случайное целое между 0 и 25
echo "<br />";
//Пример использования str_shuffle()
$string = 'abcefghijklmnopqrstuvwxyz';
print str_shuffle($string);//Смешение строки $string
?>
Функции rand() и mt_rand() принимают два параметра, где $min является наименьшим целым, с котрого начинается диапазон случайных чисел, а $max представляет максимальное число, которое ограничивает диапазон случайных чисел сверху. Функция str_shuffle получает один параметр строку и выводит ее смешанный вариант. Функция str_shuffle выполняет действия, похожие на перемешивание колоды карт.
Также для генерирования случайных уникальных значений широко используется функция uniqid(). Функция uniqid() генерирует уникальный идентификатор на основе текущего времени, выраженного в микросекундах (с помощью php.net). Использование данной функции очень удобно при создании идентификаторов сессий и ключей форм.
<?php
//Пример использования uniqid()
print uniqid();//По умолчанию
echo "<br />";
print uniqid("NETTUTS", TRUE);//Добавлен дополнительный префикс и установлено значение TRUE для more_entropy
?>
Функция uniqid() принимает два параметра. Первый добавляет префикс к результату, а второй, при значении TRUE указывает на то, что нужно сделать конец значения более неопределенным.
Генерирование случайного пароля
Существует огромное количество примеров в интернете, которые демонстрируют генерацию случайных паролей и все отлично работают. Для чего нужно генерировать случайный пароль? Ответ очень прост. Нельзя полагаться на пользователя, что он придумает достаточно надежный пароль. Генерирование случайного пароля также необходимо при регистрации пользователя или в случаях, когда пользователь теряет свой пароль. Такая практика обеспечивает получение надежного пароля сразу с самого знакомства пользователя с вашим сайтом, а также сокращает некоторое количество строк кода, когда пользователю нужно снова получить доступ.
Несколько примеров.
Пример 1
<?php
//Простая функция, которая генерирует случайный пароль
function randompassword($count){
$pass = str_shuffle('abcefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890@#%$*');
return substr($pass,3,$count);//Возвращаем пароль
}
?>
В данном примере строка смешивается с помощью str_shuffle, а затем формируется строка пароля заданной длины. Таким образом, если Вы хотите сгенерировать пароль длиной 8 символов, то нужно передать функции randompassword () значение 8.
Пример 2
<?php
//Другой пример создания случайного пароля
function anorandpass($count) {
$m_rand = mt_rand(); //генерируем случайное целое
$u_id = uniqid("MNO!@#$%^&*=+XYZ", TRUE);//создаем уникальный идентификатор с префиксом, постфиксом и повышенной энтропией
$combine = $m_rand . $u_id;// Комбинируем переменные для формирования строки
$new = str_shuffle($combine);//смешиваем строку
return substr($new, 2, $count);//возвращаем пароль
}
print anorandpass(8);
?>
Если сравнивать два примера, то первый получает статичную строку, перемешивает ее и затем возвращает, а во втором процесс имеет более динамичный характер. В примере два смешиваемая строка не является статичной, каждая ее часть генерируется случайно. В то время, как использование примера 1 будет достаточным в большинстве случаев для генерации надежного пароля, пример 2 позволяет нам быть уверенным, что длина строки и набор символов будут изменяться при использовании, что существенно снижает шансы получить копию пароля..
Давление на пользователя использовать сложный пароль для доступа к ресурсам сайта может отпугнуть посетителей или привести к отказу от регистрации. Зачастую нужно находить компромисс между желаемым трафиком и уверенностью в безопасности программного комплекса. Хорошей практикой является обеспечение возможности для ваших пользователей выбрать между созданием своего собственного пароля или генерацией случайной строки в системе.
Добавление шума к паролю для улучшения безопасности.
Добавление шума к паролю является эффективным способом для увеличения безопасности учетных записей ваших пользователей даже в случае если злодей получил доступ к вашей базе данных. Если все сделано правильно. Если злодей может получить доступ к методу добавления шума, то он может получить расширенные полномочия для управления системой. Поэтому использование техники случайных чисел при хранении пароля делают процесс захвата очень сложным, особенно в условиях, когда информация пользователей и контент хранится в разных базах данных.
Почему и как?
Наблюдения показывают, что конечный пользователь старается обеспечить себе как можно более простой способ обеспечения безопасности Обычно используются пароли, которые легко запомнить, и даже используется один пароль для доступа к нескольким сайтам. Легко запоминающиеся пароли обычно представляют собой общеупотребительные слова или некоторые виды значений (например 12345, QWERTY). Разработчики часто смеются над такой практикой, существенно изменить положение в данной ситуации нет возможности.
Для того, чтобы вэб приложение использовало добавление шума в пароль, нужно где-то хранить процедуру. Не рекомендуется использовать одинаковый шум для всех паролей базы данных, нужно генерировать для каждого пароля индивидуальный шум. Создание полноценной системы регистрации пользователей со всеми колокольчиками и свистками выходит за рамки данного урока, однако мы сделаем простую систему, которая позволит продемонстрировать генерацию шума и использование методов случайных чисел.:
1. Соединение с базой
Здесь описана структура таблицы SQL, которую мы будем использовать.
CREATE TABLE IF NOT EXISTS `users` (
`usr_id` int(11) NOT NULL AUTO_INCREMENT,
`usr_name` varchar(24) NOT NULL,
`usr_pass` varchar(32) NOT NULL,
`usr_email` varchar(255) NOT NULL,
`usr_salt` varchar(255) NOT NULL,
PRIMARY KEY (`usr_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 ;
<?php
/*db_config.php*/
//Конфигурация базы данных
$db_host ="localhost" ; //располагается на том же сервере, что и сайт
$db_name = "thedbname"; //имя таблицы базы данных
$db_usr = "username"; //имя пользователя базы данных
$db_pass = "password";//пароль пользователя
//Устанавливаем соединение с MySQL и выбираем базу данных для использования
mysql_connect($db_host, $db_usr, $db_pass) or die("MySQL Error: " . mysql_error());
mysql_select_db($db_name) or die("MySQL Error: " . mysql_error());
?>
2. Файл регистрации
<?php
/*registration.php*/
//требуется db_config.php
require ('db_config.php');
//Проверяем, готовность данных в отправленной форме
if(!empty($_POST['username']) && !empty($_POST['email']) && !empty($_POST['password'])) {
//Готовим данные
$username = mysql_real_escape_string($_POST['username']);
$email = mysql_real_escape_string($_POST['email']);
$password = mysql_real_escape_string($_POST['password']);
//Генерируем надежный уникальный шум
$salt_gen = uniqid(mt_rand());
//Комбинируем email, пароль и шум вместе
$combine = $email . $password . $salt_gen;
//md5 вычисляет хэш комбинированного пароля * Примечание: md5 используется как пример
$newpassword = md5($combine);
//Вставляем значения в базу данных
$registerquery = mysql_query("INSERT INTO users (usr_name, usr_pass, usr_email, usr_salt) VALUES ('".$username."', '".$newpassword."', '".$email."', '".$salt_gen."')") or die("MySQL Error: ".mysql_error());
//Обратная связь с пользователем о результате операции
if ($registerquery) {
echo '<h1>Успешно</h1>';
} else {
echo '<h1>Ошибка</h1>';
}
}
?>
Рассмотрим код PHP. Для упрощения мы включили конфигурационный файл нашей базы данных. PHP проверяет готовность данных в отправленной форме по значению переменных $_POST. Если они не пустые, то скрипт подготавливает данные для вставки их в базу. Затем генерируется шум с использованием uniqid() и mt_rand() и сохраняется в переменной $salt_gen. Далее комбинируется $password и шум. А затем вычисляется хэш для скомбинированного значения с помощью md5.
Также в комбинацию пароля и шума добавляется email пользователя. Если злодей получит доступ к базе данных и сумеет прочитать значение шума, то использование email в комбинации является дополнительным препятствием на его пути, потому что нужно дополнительно исследовать код программы. А насколько случаен и уникален email?
В оставшемся коде PHP происходит добавление данных в базу и вывод информационного сообщения для пользователя. Осталось только написать HTML файл для завершения системы.
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<form action="" method="post">
<label for="username">Введите имя пользователя</label>
<input type="text" name="username" /><br />
<label for="email">Email:</label>
<input type="text" name="email" /><br />
<label for="password">Пароль:</label>
<input type="password" name="password" /><br />
<input type="submit" name="submit" value="Отправить" />
</form>
</body>
</html>
Это простая HTML форма, которая предназначена для ввода имени пользователя, email, и пароля. Ничего необычного.
3. Аутентификация пользователя
Итак, у нас есть форма регистрации пользователя, которая сохраняет данные в базе. Нужно создать страницу входа на сервер, которая будет получать данные из базы и проводить аутентификацию пользователя. PHP код:
<?php
/*login.php*/
// требуется db_config.php
require ('db_config.php');
//Проверяется, заполнены ли поля формы
if(!empty($_POST['username']) && !empty($_POST['password'])) {
//Готовятся данные
$username = mysql_real_escape_string($_POST['username']);
$password = mysql_real_escape_string($_POST['password']);
//Получаем строку, которая соответствует имени пользователя в форме
$grab_row = mysql_query("SELECT * FROM users WHERE usr_name = '".$username."'") or die ("MySQL Error: ".mysql_error());
//Если получена только одна строка
if (mysql_num_rows($grab_row) == 1) {
//Создаем массив из полей
$row = mysql_fetch_array($grab_row);
//Сохраняем шум пользователя
$salt = $row['usr_salt'];
//Сохраняем email
$email = $row['usr_email'];
//Комбинируем email, пароль, и шум
$combine = $email . $password . $salt;
//Вычисляем хэш скомбинированного значения
$auth_pass = md5($combine);
//Проверяем базу данных снова на наличие строки, соответствующей имени пользователя и скомбинированного значения
$checklogin = mysql_query("SELECT * FROM users WHERE usr_name = '".$username."' AND usr_pass = '".$auth_pass."'") or die("MySQL Error: ".mysql_error());
//Если получена только одна строка, то аутентификация прошла успешно
if(mysql_num_rows($checklogin) == 1) {
echo '<h1>Вы вошли в систему!</h1>';
} else {
echo '<h1>Извините, но вам отказано во входе в систему!</h1>';
}
} else {
echo '<h1>Ошибка базы данных!</h1>';
}
}
?>
Все что мы делаем в файле login.php - это получаем данные из формы, получаем соответствующие данные из базы, рекомбинируем строку пароля (используя email, пароль и шум) и повторно вычисляем хэш. Затем производится проверка с помощью поиска по базе данных на наличие записи соответствующей имени пользователя и хэша скомбинированного пароля. И сообщается пользователю об успехе или провале операции. А вот код HTML:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<form action="" method="post">
<label for="username">Введите имя пользователя</label>
<input type="text" name="username" /><br />
<label for="password">Введите пароль<label>
<input type="password" name="password" /><br />
<input type="submit" name="submit" value="Отправить" />
</form>
</body>
</html>
Запутывание кода PHP
Простой пример, который в тоже время достаточно сложен для анализа:
<?php $a1c0_z2='c'.$a91.'tion ';$a91="a";$vly_ti="us".'ed';$j1h_32_a=' to';$z1b_1=$a91." ";$lz32i_4="“O"."bfus";$g1k0p='que ';$lv83="t".'ec'.'hni';$lFa='i'.'s ';if($z1b_1==$a91." ")$rx_b_1='a';$glccUv=" complic".$rx_b_1.'te ';$xl1ttf='code ';$zljal1="in such a";if($z1b_1==$a91." ")$s1b_1='a';$p1x2 =" w".$s1b_1."y ";$il_7x=' '.$b1zE_.'t i'.$l1yes;$b1zE_="i";$l1yes="s";$nltotry_ws='st'.$s1b_1."n";$yl5B_='thαt ';$dlno=' not ';$m1tomanythings="under";if($s1b_1=='a')$bz_1=$s1b_1;$Ozaq="d".$bz_1."ble"";echo base64_decode("JiM4MjIwO09iZnVzY3Rpb24mIzgyMDE7aXMmIzgyMDE7YSYjODIwMTt0ZWNobmlxdWUmIzgyMDE7dXNlZCYjODIwMTt0byYjODIwMWNvbXBsaWNhdGUmIzgyMDE7Y29kZSYjODIwMTtpbiYjODIwMTtzdWNoJiM4MjAxO2EmIzgyMDE7d2F5JiM4MjAxO3RoJmFscGhhO3QmIzgyMDE7aSYjODIwMTt0JiM4MjAxO2kmIzgyMDE7bm90JiM4MjAxO3VuZGVyc3RhbmRhYmxlJnF1b3Q7");?>
Как вы можете видеть, сложно понять, что делает данный код. В нем нет ясных названий переменных, нет комментариев, нет структуры и он размещен в одной строке. Но даже если мы не можем распознать код, то машина продолжает его выполнять. Он работает. Данная одна срока сплошного хаоса просто выводит строку “Obfusction is a technique used to complicate code in such a way that i t i not understandable.” (Запутывание кода - это метод, который используется для того, чтобы усложнить понимание того, что делает код).
Запутывание кода имеет свои плюсы и минусы. Его назначение - разубедить того, кто хочет разобраться в алгоритме программы, взглянув на код. Такой подход хорошо работает с теми, кто плохо знает языки программирования. Однако, любой, кто имеет хотя бы базовые знания PHP, может распутать выше приведенный код и узнать, что он делает. И весь процесс может занять немного времени. Запутывание кода не является методом шифрования. Кроме того, запутанный код, как правило, занимает больше места на диске.
Как можно запутать код?
Существует два способа запутать код. Первый - сделать все руками. Написание запутанного кода отнимает много времени. Кроме того, даже автору очень сложно выявить и исправить ошибку в таком коде. Второй способ - использовать программное обеспечение, которое сделает это для вас.
Несколько советов по запутыванию кода
Всегда сохраняйте ясный оригинал для себя.
Чем больше случайности в вашей технике кодирования - тем лучше.
Исключайте все пробелы, где они не нужны.
Используйте коды символов и пробелов
Чем сложнее код - тем лучше
Пренебрегайте структурой там, где это не наносит вреда функционированию кода
Не используйте понятных имен переменных, классов и пространств имен
Чем меньше кода вы используете повторно, тем лучше
Запутывать или не запутывать?
Все зависит от ваших планов. Если Вы планируете продавать ваш скрипт PHP (или другое программное обеспечение) нужно его лицензировать. Это первая линия обороны, которая мешает использовать ваше программное обеспечение кому бы то нибыло не по назначению. Однако, вы можете планировать запутать часть, или весь код по другим причинам. Но если вы действительно озадачены безопасностью вашего кода, то может быть стоит использовать шифрование вместо запутывания..
Криптография в PHP
Почти в каждом веб приложении ощущается некоторое присутствие криптографии (например, почтовые клиенты и вэб сайты). Разработчикам нужно знать о практическом применении криптографии. PHP обеспечивает несколько фундаментальных и практичных функций, которые можно использовать для шифрования данных. В данном разделе мы разберемся с алгоритмом однопроходного хэширования и коснемся шифрования на основе симметричных ключей.
Однопроходное хэширование
Однопроходное хэширование используется для безопасного хранения паролей и проверки целостности данные в файлах.
SHA-1, 2, и 3
Семейство алгоритмов хэширования SHA в настоящее время пользуется популярностью, особенно SHA-1. Даже не смотря на то, что алгоритм SHA-1 имеет ряд недостатков, он активно используется.
<?php
///Однопроходное хэширование SHA-1
$string = "Netuts is Awesome";
$hash = sha1($string);
//или
$hash2 = hash('sha1', $string);
echo $hash."<br />";
echo $hash2."<br /><br />";
//Выведет: 42d2f15c3f92d28d7d58776e5d81b800f662cc6c
?>
В PHP пользуется почетом SHA-2, который требует версии PHP 5.1.2 или новее. SHA-2 более продвинутый алгоритм по сравнению с SHA-1 и может использовать различную длину хэш кода.
<?php
$string_sha256 = "Nettuts is Awesome";
$string_sha384 = "Nettuts is Awesome";
$string_sha512 = "Nettuts is Awesome";
$hash_sha256 = hash('sha256', $string_sha256);
$hash_sha384 = hash('sha384', $string_sha384);
$hash_sha512 = hash('sha512', $string_sha512);
echo $hash_sha256."<br />";
echo $hash_sha384."<br />";
echo $hash_sha512."<br />";
/* Выводят соответственно:
sha256 : 09074adc0d70e15b88494643e29c2836e1ab94a21989691dec594cb0bd742ebc
sha384 : 8535470750df54a78701d4bfe0451f9799057a5bc101944a32480d2436e8b95440bce3bcab3f9ce107b0b92d9595ae32
sha512 : c2e6dce873a71800b862791e56b480b976bb26cd3136c02da510c3905caa49b7b9e9260549976e1e741cc93e4569a611f2030d3b7104c6c6c2ff9e6c9bf0946a
*/
?>
Хэш функция вызывается с помощью hash(algorithm, string). Новая версия PHP функции hash() может быть вызванная с указанием любого однопроходного алгоритма хэширования, который поддерживает PHP (например md5, sha-1, haval, ghost). Если Вы хотите увидеть все зарегистрированные алгоритмы хэширования, то можно использовать:
<?php
//Для PHP5 >= 5.1.2
print_r(hash_algos());
?>
Алгоритм SHA-3 все еще разрабатывается и проходит стандартизацию.
Шифрование на основе ключей
Методы шифрования на основе симметричных ключей используются для обмена сообщения между двумя точками, в одной из которых происходит кодирование информации, а в другой - расшифровка.
HMAC
В действительности HMAC является смесью между однопроходным хэшированием и шифрованием на основе ключей. Система безопасности на основе HMAC полагается на размер используемого ключа и на стойкость функции хэширования.
<?php
$string_hmac = "Nettuts is Awesome";
//hash_hmac(algorithm, string to hash, key)
$hmac = hash_hmac('sha1', $string_hmac, 'secret');
echo $hmac."<br />";
?>
Заключение
Важно помнить, что безопасность достигается использованием не одного метода, а сочетанием нескольких методов. Случайные числа, добавление шума, запутывание кода , криптография с помощью творческого сочетания позволяют добиться достаточно высокого уровня безопасности и существенно усложнить дело злодеев.