Сегодня все ещё большинство сайтов работает по протоколу http, а не https. Это хуже в плане безопасности, но дешевле и проще в поддержке. Безопасность аутентификации на таких сайтах — проблема, так как чаще всего она реализуется простой отправкой формы c логином/паролем на сервер, где хэш пароля сверяется c хэшем, который соответствует указанному логину. Входя на такой сайт в открытой Wi-Fi сети или другом публичном месте любой кто умеет пользоваться google сможет без особых усилий перехватить ваш пароль. Некоторые идут чуть дальше, отправляя на сервер хэш пароля, но и это не на много лучше так как имея радужную таблицу (особенно для md5) можно попробовать найти пароль, имея не нулевой на успех, и каждый день вероятность успеха растет.
В этой статье я предлагаю альтернативный, более сложный способ аутентификации, при котором пароль ни в чистом виде, ни в виде хэша не светится, а заодно такой подход практически исключит необходимость в добавлении captcha к форме входа.
Всё описанное уже используется на практике, поэтому примеры будут реальные, и учитывающие контекст их использования.
Аутентификация будет проходить в два этапа c использованием AJAX.
1 Этап
Подготовка, отправляем на сервер sha224 хэш логина, получаем случайный хэш. JavaScript пример:
$.ajax(
base_url+'/api/System/user/login',
{
cache : false,
data : {
login : hash('sha224', login)
},
success : function(random_hash) {
//Get random hash
},
error : function() {
//Error
}
}
);
hash() — это JavaScript обертка, которая делает то же самое, что и соответствующая php функция.
Случайный хэш генерируется на сервере. PHP пример:
$random_hash = hash('sha224', microtime(true));
Случайный хэш запоминается на сервере, и связывается c логином.
2 Этап
Собственно, аутентификация. JavaScript пример (вместе c первым):
**
* Login into system
*
* @param {string} login
* @param {string} password
*/
function login (login, password) {
$.ajax(
base_url+'/api/System/user/login',
{
cache : false,
data : {
login: hash('sha224', login)
},
success : function(random_hash) {
if (random_hash.length == 56) {
$.ajax(
base_url+"/api/user/login",
{
cache : false,
data : {
login : hash('sha224', login),
auth_hash : hash(
'sha512',
hash('sha224', login)+hash('sha512', hash('sha512', password)+public_key)+navigator.userAgent+random_hash
)
},
success : function(result) {
if (result == 'reload') {
location.reload();
} else {
//Error
}
},
error : function() {
//Error
}
}
);
} else {
//Error
}
},
error : function() {
//Error
}
}
);
}
Как можно увидеть, для пароля используется sha512 хэш. public_key — это глобальная переменная, которая содержит строчку длиной в 56 знаков, и используется как соль. Эта переменная общедоступна, но благодаря тому, что используется относительно сложный алгоритм получения хэша sha512 (дважды) — генерация радужных таблиц для каждого отдельного сайта будет достаточно сложной, дорогой и длительной процедурой, чтобы это имело большой смысл.
Имея хэш логина, мы можем найти на сервере соответствующий пароль, случайный хэш, сгенерировать соответствующий auth_hash и сравнить c тем, который был получен от пользователя. В случае совпадения заводим сессию, и обновляем страницу пользователя. Реализация обработки ошибок остается на ваше рассуждение.
Что тогда c регистрацией
Очень просто, генерировать пароль за пользователя, и отправлять на почту. Любой уважающий себя и пользователя почтовый сервис поддерживает доступ к почте по шифрованному каналу. Пользователь при желании сможет сменить пароль.
Смена пароля/восстановление
Восстановление пароля в данной схеме тоже производится на почтовый ящик.
Смену пароля можно реализовать следующим образом:
- со старого и нового паролей генерируем хэши как в примере выше:
hash('sha512', hash('sha512', password)+public_key)
- делаем посимвольный XOR обоих паролей, например, c помощью такой функции xor_string():
String.prototype.replaceAt=function(index, symbol) { return this.substr(0, index)+symbol+this.substr(index+symbol.length); }; function xor_string (string1, string2) { var len1 = string1.length, len2 = string2.length; if (len2 > len1) { var tmp = string1; string1 = string2; string2 = tmp; tmp = len1; len1 = len2; len2 = tmp; } for (var i = 0; i < len1; ++i) { var pos = i % len2; string1 = string1.replaceAt(i, String.fromCharCode(string1.charCodeAt(i) ^ string2.charCodeAt(pos))); } return string1; }
- Отправляем на сервер два хэша. Один полученный в результате xor_string, второй от хэша текущего пароля и id текущей сессии пользователя. JavaScript пример:
/** * Password changing * * @param {string} current_password * @param {string} new_password */ function change_password (current_password, new_password) { if (!current_password) { //Error return; } else if (!new_password) { //Error return; } else if (current_password == new_password) { //Error return; } current_password = hash('sha512', hash('sha512', current_password)+public_key); new_password = hash('sha512', hash('sha512', new_password)+public_key); $.ajax( base_url+'/api/System/user/change_password', { cache : false, data : { verify_hash : hash('sha224', current_password+session_id), new_password : xor_string(current_password, new_password) }, success : function(result) { if (result == 'OK') { //Success } else { //Error } }, error : function() { //Error } } ); }
- На сервере, зная что за пользователь, мы легко генерируем verify_hash. Если он совпадает c полученным от пользователя — текущий пароль был введен корректно, делаем посимвольный xor хэша строки текущего пароля пользователя, и new_password, полученного от пользователя. Таким образом мы восстанавливаем хэш нового пароля пользователя, которым и заменяем текущий. PHP пример аналога xor_string из JavaScript примера:
function xor_string ($string1, $string2) { $len1 = mb_strlen($string1); $len2 = mb_strlen($string2); if ($len2 > $len1) { list($string1, $string2, $len1, $len2) = [$string2, $string1, $len2, $len1]; } for ($i = 0; $i < $len1; ++$i) { $pos = $i % $len2; $string1[$i] = chr(ord($string1[$i]) ^ ord($string2[$pos])); } return $string1; }
Дополнительные меры безопасности
Дополнительно рекомендую добавлять в запрос id текущей сессии пользователя, и при его отсутствии или не совпадении c текущей сессией обнулять данные POST запроса.
На этом всё
Код можете свободно использовать в своих целях, менять типы и размеры хэшей для усложнения или ускорения процедуры.
Буду рад замечаниям/пожеланиям, спасибо что дочитали до этого места.
Автор: nazarpc