Безопасная аутентификация на сайте без https

от автора

Сегодня все ещё большинство сайтов работает по протоколу 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 запроса.

На этом всё

Код можете свободно использовать в своих целях, менять типы и размеры хэшей для усложнения или ускорения процедуры.
Буду рад замечаниям/пожеланиям, спасибо что дочитали до этого места.

ссылка на оригинал статьи http://habrahabr.ru/post/171347/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *