C#SSO哈希迁移到PHP

时间:2018-04-03 17:39:39

标签: c# php pbkdf2

我正在使用PHP中的SSO实现,该实现对使用C#编写的系统进行身份验证。这里有一些伪代码来演示:

$token = "MqsXexqpYRUNAHR_lHkPRic1g1BYhH6bFNVPagEkuaL8Mf80l_tOirhThQYIbfWYErgu4bDwl-7brVhXTWnJNQ2";
$id = "bob@company.com";
$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 = "bob@company.com";
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

谢谢!

2 个答案:

答案 0 :(得分:3)

我根本不知道php,但我仍然可以帮助。首先,正如我的评论中所述,C#中的buildToolsVersion "26.0.1" 使用SHA1作为哈希函数,而不是SHA256,与文档所说的无关。

接下来,Rfc2898DeriveBytes(和UrlTokenDecode)是我在实践中很少看到的奇怪的事情。它将常规base64转换为“url safe”版本,如下所示:

  • 将'+'替换为' - '
  • 将'/'替换为'_'
  • 删除填充(末尾为'=='),并将删除的填充的长度作为数字附加为最后一个字符(如果没有填充 - 它仍然附加“0”)。这一步对我没有任何意义,但它是如何运作的。

因此要复制,您需要Encode,替换,删除填充,然后添加填充长度作为字符。因此,如果您的base64字符串以base64_encode结尾 - 您将其删除并在末尾添加“2”。如果没有填充 - 你添加“0”。

因此,为了解码该字符串,您需要进行替换,然后删除最后一个字符并将该字符添加到该字符所指示的末尾。

所以字符串

==

正常base64是   MqsXexqpYRUNAHR / lHkPRic1g1BYhH6bFNVPagEkuaL8Mf80l / tOirhThQYIbfWYErgu4bDwl + 7brVhXTWnJNQ ==

然后,我不知道你为什么这样做

MqsXexqpYRUNAHR_lHkPRic1g1BYhH6bFNVPagEkuaL8Mf80l_tOirhThQYIbfWYErgu4bDwl-7brVhXTWnJNQ2

只需删除它(虽然因为我不知道php - 你可能有某种原因这样做,但我无法想象哪个,所以要小心)。

然后作为其他答案状态 - hash_pbkdf2()的最后一个参数应为true。

进行此更改后,您的代码将起作用(我使用的令牌已转换为普通的base64字符串):

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

产生预期答案(在正常的base64中 - 你需要“url encode”它以获得完全匹配)。

答案 1 :(得分:2)

我已经烧掉了比此更多的时间,但是虽然这不是一个完整的答案,但我找到的几个主要问题是:

  1. 散列算法是SHA1,而不是SHA256。 [正如@Evk已经注意到的]
  2. HttpServerUtility.UrlToken(De|En)code()使用需要复制的base64的url-safe变体。

    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,否则您将获得十六进制编码的哈希而不是原始字节。

  5. 我真正建议的是询问您的供应商是否对实现此目标有任何见解。有可能是某人已经拥有并通过他们的集成解决了这个问题。

    编辑:使用来自@Evk的答案中的新信息,这里有一些简单命名的函数,用于与C#的辉煌 base64 URL编码兼容:

    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 = "bob@company.com";
    $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有最重要的部分排序。