Хеширование единого входа в C # перенесено в переполнение стека

Я работаю над реализацией единого входа в PHP, которая аутентифицируется в системе, написанной на C #. Вот некоторый псевдокод для демонстрации:

$token = "MqsXexqpYRUNAHR_lHkPRic1g1BYhH6bFNVPagEkuaL8Mf80l_tOirhThQYIbfWYErgu4bDwl-7brVhXTWnJNQ2";
$id = "[email protected]";
$ssokey = "7MpszrQpO95p7H";
$idAndKey = $id . $ssokey;
$salt = base64_decode(substr($token, 0, -1));
$hashed = hash_pbkdf2("sha256", $idAndKey, mb_convert_encoding($salt, 'UTF-16LE'), 1000, 24, false);
$data = base64_encode($hashed);

Это выводит: NWZiMTBhZmNhNTlmYzMxMTEzMThhZmVl

Вот версия C # из системы, с которой я интегрируюсь:

var token = "MqsXexqpYRUNAHR_lHkPRic1g1BYhH6bFNVPagEkuaL8Mf80l_tOirhThQYIbfWYErgu4bDwl-7brVhXTWnJNQ2";
var id = "[email protected]";
var ssokey = "7MpszrQpO95p7H";
string idAndKey = id + ssokey;
var salt = HttpServerUtility.UrlTokenDecode(token);
var pbkdf2 = new Rfc2898DeriveBytes(idAndKey, salt) {IterationCount = 1000};
var key = HttpServerUtility.UrlTokenEncode(pbkdf2.GetBytes(24));
Console.WriteLine(key.ToString());

Это выводит: aE1k9-djZ66WbUATqdHbWyJzskMI5ABS0

Я не могу понять, как заставить мой код PHP делать то же самое. У меня есть ощущение, что это в salt поколение.

Я пытался перевести C # HttpServerUtility.UrlTokenDecode Функция в PHP выглядит так:

function UrlTokenDecode($token) {
$numPadChars = substr($token, -1);

// add the padded count to the end
$salt = substr($token, 0, -1) . $numPadChars;

// Transform the "-" to "+", and "*" to "/"$salt = str_replace('-', '+', str_replace('*', '/', $salt));

// base64_decode
$salt = base64_decode($salt);

return $salt;
}

Это не привело меня туда, куда мне нужно было идти. Halp!

Это для Absorb LMS. Документация их методов здесь: https://support.absorblms.com/hc/en-us/articles/222446647-Incoming-Absorb-Single-Sign-On#Methods

Спасибо!

1

Решение

Я вообще не знаю php, но все же могу помочь, я думаю. Во-первых, как указано в моем комментарии, Rfc2898DeriveBytes в C # в качестве хеш-функции используется SHA1, а не SHA256, не имеет значения, что говорится в вашей документации.

Следующий, UrlTokenDecode (а также Encode) довольно странная вещь, которую я редко видел на практике. Он преобразует обычную base64 в «безопасную для URL» версию следующим образом:

  • заменяет ‘+’ на ‘-‘
  • заменяет ‘/’ на ‘_’
  • удаляет заполнение (‘==’ в конце) и добавляет длину удаленного заполнения как число как последний символ (если не было дополнения — он все равно добавляет «0»). Этот шаг не имеет никакого смысла для меня, но это то, как он работает.

Так что для тиражирования нужно base64_encode, замените, удалите отступы, а затем добавьте длину отступа как символ. Так что если ваша строка base64 закончилась == — вы удалите это и добавьте «2» в конце. Если там не было отступов — вы добавляете «0».

Таким образом, чтобы декодировать эту строку, вам нужно сделать обратную замену, затем удалить последний символ и добавить столько «=» в конец, как указано этим символом.

Так что строка

MqsXexqpYRUNAHR_lHkPRic1g1BYhH6bFNVPagEkuaL8Mf80l_tOirhThQYIbfWYErgu4bDwl-7brVhXTWnJNQ2

В обычном base64 есть
MqsXexqpYRUNAHR / lHkPRic1g1BYhH6bFNVPagEkuaL8Mf80l / tOirhThQYIbfWYErgu4bDwl + 7brVhXTWnJNQ ==

Тогда я понятия не имею, зачем ты это делаешь

mb_convert_encoding($salt, 'UTF-16LE')

Просто удалите его (хотя я не знаю php — возможно, есть какая-то причина, по которой вы это делаете, но я просто не представляю, что именно, так что будьте осторожны).

Тогда, как говорится в другом ответе, последний аргумент hash_pbkdf2 () должен быть истинным.

После внесения этих изменений ваш код будет работать (я использовал токен, уже преобразованный в обычную строку base64):

$token = "MqsXexqpYRUNAHR/lHkPRic1g1BYhH6bFNVPagEkuaL8Mf80l/tOirhThQYIbfWYErgu4bDwl+7brVhXTWnJNQ==";
$id = "[email protected]";
$ssokey = "7MpszrQpO95p7H";
$idAndKey = $id . $ssokey;
$salt = base64_decode($token);
$hashed = hash_pbkdf2("sha1", $idAndKey, $salt, 1000, 24, true);
$data = base64_encode($hashed);
echo $data;

выдает ожидаемый ответ (в обычном base64 — вам нужно «кодировать URL», чтобы получить точное совпадение).

3

Другие решения

Я уже потратил на это больше времени, чем следовало бы, но хотя это не полный ответ, я обнаружил несколько основных проблем:

  1. Алгоритм хэширования — SHA1, а не SHA256. [как @Evk уже отметил]
  2. HttpServerUtility.UrlToken(De|En)code() используйте URL-безопасный вариант base64, который нужно тиражировать.

    function base64url_encode($bin) {
    return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($bin));
    }
    
    function base64url_decode($str) {
    return base64_decode(str_replace(['-', '_'], ['+', '/'], $str));
    }
    
  3. Когда вы декодируете токен, в результате получается бинарная строка, и попытка запустить ее через mb_convert_encoding для изменения порядка байтов [я обнаружил, что и этот ужасный пост в блоге] не будет делать то, что вы думаете. Вы можете попробовать следующее, но токен имеет нечетное количество байтов, что проблематично, независимо от того, как вы на него смотрите. [править: есть только голый \x0d возврат каретки в конце?]

    function swapEndian16($in) {
    $out = '';
    foreach(str_split($in, 2) as $chunk) {
    $out .= $chunk[1] . $chunk[0];
    }
    return $out;
    }
    
  4. Последний аргумент hash_pbkdf2() должно быть trueиначе вы получите хеш-код в шестнадцатеричном формате, а не необработанные байты.

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

Редактировать: С новой информацией из ответа @ Evk, вот несколько нахально названных функций для совместимости с C # блестящий кодировка URL base64:

function dumb_base64url_encode($bin) {
return preg_replace_callback(
'/(=*)$/',
function($matches){
return strlen($matches[0]);
},
str_replace(
['+', '/'],
['-', '_'],
base64_encode($bin)
),
1
);
}

function dumb_base64url_decode($str) {
return base64_decode(
str_replace(
['-', '_'],
['+', '/'],
substr($str, 0, -1)
)
);
}

Итак, теперь с неотправленным токеном:

$token = "MqsXexqpYRUNAHR_lHkPRic1g1BYhH6bFNVPagEkuaL8Mf80l_tOirhThQYIbfWYErgu4bDwl-7brVhXTWnJNQ2";
$id = "[email protected]";
$ssokey = "7MpszrQpO95p7H";
$idAndKey = $id . $ssokey;
$salt = dumb_base64url_decode($token);
$hashed = hash_pbkdf2("sha1", $idAndKey, $salt, 1000, 24, true);
$data = dumb_base64url_encode($hashed);
echo $data; // output: aE1k9-djZ66WbUATqdHbWyJzskMI5ABS0

И не волнуйтесь, чей ответ пометить правильно, я думаю, что у @ Evk самые важные биты отсортированы.

2

По вопросам рекламы [email protected]