Я работаю над реализацией единого входа в 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
Спасибо!
Я вообще не знаю php, но все же могу помочь, я думаю. Во-первых, как указано в моем комментарии, Rfc2898DeriveBytes
в C # в качестве хеш-функции используется SHA1, а не SHA256, не имеет значения, что говорится в вашей документации.
Следующий, UrlTokenDecode
(а также Encode
) довольно странная вещь, которую я редко видел на практике. Он преобразует обычную base64 в «безопасную для URL» версию следующим образом:
Так что для тиражирования нужно 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», чтобы получить точное совпадение).
Я уже потратил на это больше времени, чем следовало бы, но хотя это не полный ответ, я обнаружил несколько основных проблем:
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));
}
Когда вы декодируете токен, в результате получается бинарная строка, и попытка запустить ее через mb_convert_encoding для изменения порядка байтов [я обнаружил, что и этот ужасный пост в блоге] не будет делать то, что вы думаете. Вы можете попробовать следующее, но токен имеет нечетное количество байтов, что проблематично, независимо от того, как вы на него смотрите. [править: есть только голый \x0d
возврат каретки в конце?]
function swapEndian16($in) {
$out = '';
foreach(str_split($in, 2) as $chunk) {
$out .= $chunk[1] . $chunk[0];
}
return $out;
}
Последний аргумент 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 самые важные биты отсортированы.