【问题标题】:C# SSO hashing migrated to PHPC# SSO 哈希迁移到 PHP
【发布时间】:2018-09-13 03:17:46
【问题描述】:

我正在使用 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;
}

这并没有让我到达我需要去的地方。哈!

这是用于吸收 LMS。他们的方法的文档在这里:https://support.absorblms.com/hc/en-us/articles/222446647-Incoming-Absorb-Single-Sign-On#Methods

谢谢!

【问题讨论】:

  • 一件事是Rfc2898DeriveBytes 使用 sha1 作为哈希函数,而不是 sha256。
  • 谢谢! Absorb 文档特别说明了 sha256,这让我很失望。我已经更新了我的代码,但仍然没有得到想要的结果。还有其他想法吗?

标签: c# php pbkdf2


【解决方案1】:

我根本不懂 php,但仍然可以帮助我思考。首先,正如我在评论中所述,C# 中的Rfc2898DeriveBytes 使用 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 = "bob@company.com";
$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 编码”以获得完全匹配)。

【讨论】:

  • 这太棒了。在您的回答和@Sammitch(我希望我可以将两者都标记为正确)之间,我现在得到了正确的输出。 sha1 是解开谜题的关键,但正确的编码/解码(我以为我已经这样做了)会有所作为。
  • 呃,base64 末尾有填充的东西是 super 愚蠢的。至少在 PHP 中,您可以在解码之前删除该“填充”字符而不会产生不良影响。我在答案中添加了一些实用的功能,应该很有用。
  • @Sammitch 是的,直到这个问题我才知道存在这种方法。好在几乎没有人真正使用那个奇怪的“安全”base64。
  • URL 安全的 base64 实际上开始经常出现,主要是在 JWT 实现中。奇怪的部分是添加一个用于填充的数字,而不是仅仅删除它。如果您的 base64 解码函数 需要 填充,您可以轻松地将字符串填充为 4 的倍数。¯\_(ツ)_/¯
  • 当然,url safe base64 没问题,但这个特定的实现不是。
【解决方案2】:

我已经在这方面花费了比我应该花更多的时间,但是虽然这不是一个完整的答案,但我发现的几个主要问题是:

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

    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# 的 brilliant 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 已经对最重要的部分进行了排序。

【讨论】:

  • 谢谢@Sammitch。这看起来很棒。我意识到我们还没有整理出未“更正”的令牌。虽然这不是一项非常复杂的任务,但我很欣赏你的回答的彻底性
猜你喜欢
  • 2012-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-22
  • 2010-11-04
  • 1970-01-01
  • 1970-01-01
  • 2019-03-23
相关资源
最近更新 更多