Будни разработчика

Сейчас у меня стадия активной переработки проекта анонимного поиска работы — GeekJob.ru

Этот проект написан на ASP.NET еще в далеком 2012 году, крутится на Windows Server, хранит данные в MS SQL. И сейчас стоит задача его кардинально переработать и сделать супер модным молодежным. Чтобы все было пушка, изи катка и вот это вот все…

Я ничего не имею против C# и .NET, но я лично привык делать веб сервисы на скриптовых языках. Я считаю что соотношение скорости разработки, производительности и затрат ресурсов с применением PHP и/или Python самое оптимальное, особенно если у тебя очень мало человеческих ресурсов.

Я фанат SOA и использую разные инструменты для решения задач. Где-то я использую PHP, так как он лучше подходит для задачи. Где-то Python. Где-то Node.js, а где-то можно и Go, если прям по другому никак. Сейчас это монолит. Постепенно части сервиса выносятся и переписываются в виде отдельных сервисов и микросервисов.

Одной из проблем оказалась авторизация. В ASP .NET используется какой-то стандартный модуль Membership (я не .NET разработчик, так что могу путаться в терминах и названиях, поправляйте меня смело).

Честно искал ответ на стековерфлоу, но так и не нашел (либо я не так искал). Пришлось вспомнить юность и пореверсинжинирить (хоспади какое слово).

В базе есть таблица

[dbo].[aspnet_Membership]

В этой таблице есть 2 поля

[Password], [PasswordSalt]

Собственно в поле Password лежит base64 хеш пароля. В поле salt, понятное дело, лежит соль для этого пароля.

Вопрос — как повторить авторизацию на PHP?

Я нашел следующий код генерации пароля из ASP .NET

public string EncodePassword(string password, string salt)
{
byte[] bytes = Encoding.Unicode.GetBytes(password);
byte[] src = Encoding.Unicode.GetBytes(salt);
byte[] dst = new byte[src.Length + bytes.Length];

Buffer.BlockCopy(src, 0, dst, 0, src.Length);
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);

HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");

byte[] inArray = algorithm.ComputeHash(dst);

return Convert.ToBase64String(inArray);
}

Что видим:

  • Соль примешивается перед паролем
  • Соль и пароли обрабатываются в виде байтовых массивов
  • Результат возвращается в виде base64
  • Используется криптоалгоритм SHA1

Что у нас есть — пароль и соль и они уже лежать в базе в base64. Значит нам надо разкодировать соль, склеить с паролем и закриптовать через SHA1.

PHP аналог генерации хеша для паролей как в ASP.NET membership модуле

И так, нам нужно плаинтекст пароль представить в виде набора байт, сцепить с солью и вычислить SHA1. Как оказалось это довольно просто в итоге:

function get_hash4pass(string $password, string $salt) :string
{
return base64_encode(sha1(
base64_decode($salt)
. pack('v*', ...unpack('C*', $password))
, true
));
}

Ну и когда у вас есть хеш из базы и хеш из данной функции, вы просто сравниваете их:

function valid_password(string $password, string $hashed_password, string $salt) :bool
{
$hash = get_hash4pass($password, $salt);
return hash_equals($hash, $hashed_password);
}

В итоге не пришлось ломать текущую авторизацию в коде на ASP .NET и появилась возможность писать сервисы на PHP, которые могут авторизовать пользователя используя текущий алгоритм.

Возможно кому-то это пригодится.