Javascript Mismatch 中的 PHP Pack/Unpack 实现

Posted

技术标签:

【中文标题】Javascript Mismatch 中的 PHP Pack/Unpack 实现【英文标题】:PHP Pack/Unpack implementation in Javascript Mismatch 【发布时间】:2017-05-30 01:48:26 【问题描述】:

根据this question's related answer,我正在尝试组合一个类似于此 php 过程的打包/解包解决方案,但是在 Nodejs (javascript) 中使用 md5 和 bufferpack

这里是 PHP 方法(改编自 DaloRADIUS:

  $challenge = 'c731395aca5dcf45446c0ae83db5319e';
  $uamsecret = 'secret';
  $password = 'password';

  $hexchal = pack ("H32", $challenge);
  $newchal = pack ("H*", md5($hexchal . $uamsecret));
  $response = md5("\0" . $password . $newchal);
  $newpwd = pack("a32", $password);
  $pappassword = implode ("", unpack("H32", ($newpwd ^ $newchal)));

  echo "Response: ---> ", $response, "\n";
  echo "New Password: ---> ", $newpwd, "\n";
  echo "Pap Password: ---> ", $pappassword, "\n";

以上内容与这些相呼应:

以上明文:

Response: ---> 2d4bd27184f5eb032641137f728c6043
New Password: ---> password
Pap Password: ---> 356a1fb08f909fc400dfe448fc483ce3

在 Javascript 中,这是我现在正在做的事情:

  var challenge = 'c731395aca5dcf45446c0ae83db5319e';
  var uamsecret = 'secret';
  var password = 'password';

  var hexchal = pack.pack("H32", challenge);
  var newchal = pack.pack("H*", md5(hexchal + uamsecret));
  var response = md5("\0" + password + newchal);
  var newpwd = pack.pack("a32", password);
  var pappassword = pack.unpack("H32", (newpwd ^ newchal)).join("");

  console.log("Response: --> ", response);
  console.log("New Password: -->", newpwd);
  console.log("Pap Password: --->", pappassword);

结果如下:

在 JSON 中:

明文:

Response: -->  e8a54a55cbcd81dbc2bdfd9b197d62af
New Password: --> <Buffer >
Pap Password: ---> NaN

以上所有sn-ps都可以在这里找到:RadiusNES

我对整个过程的理解不是最好的,我会欣赏见解以及我哪里出错了。

为什么会出现不匹配?

【问题讨论】:

请尽可能尝试在问题正文中将纯文本作为格式化代码发布。对于某些人来说,这些屏幕截图可能非常难以阅读:它们依赖于屏幕阅读器或翻译工具。 @tadman 好的,很快就会更新。 这没什么大不了的,但是尝试通过输入长散列来重现您的结果对任何人来说都不是一件有趣的事情。我认为您需要更仔细地查看用于组合响应的前体组件,hexchalnewchal,以确保它们在两边都相同。 @tadman 我已经更新了问题,并将仔细研究hexchalnewchal 首先检查那些的 MD5 值。我认为 PHP 可能会用 0 字节 (NULL) 填充您的字符串,JavaScript 可能会截断。 【参考方案1】:

翻译不起作用,因为PHP Pack function 使用不同的格式字符串并返回字符串,而Javascript bufferpack module 返回数组。你也不能在 Javascript 中异或字符串。

虽然可能有模块可以做你想做的事,但我有自己的函数来解析十六进制字符串。我也喜欢修改不是每个人都同意的原型,但可以将它们转换为常规函数。

String.prototype.pad = function( length ,padding ) 

    var padding = typeof padding === 'string' && padding.length > 0 ? padding[0] : '\x00'
        ,length = isNaN( length ) ? 0 : ~~length;

    return this.length < length ? this + Array( length - this.length + 1 ).join( padding ) : this;



String.prototype.packHex = function() 

    var source = this.length % 2 ? this + '0' : this
        ,result = '';

    for( var i = 0; i < source.length; i = i + 2 ) 
        result += String.fromCharCode( parseInt( source.substr( i , 2 ) ,16 ) );
    

    return result;



var challenge = 'c731395aca5dcf45446c0ae83db5319e'
    ,uamsecret = 'secret'
    ,password = 'password';

var hexchal = challenge.packHex();
var newchal = md5( hexchal + uamsecret ).packHex();
var response = md5( '\0' + password + newchal );
var newpwd = password.pad( 32 );
var pappassword = '';
for( var i = 0; i < newchal.length; i++ ) 
    pappassword += ( newpwd.charCodeAt( i ) ^ newchal.charCodeAt( i ) ).toString( 16 );


console.log("Response: --> ", response);
console.log("New Password: -->", newpwd);
console.log("Pap Password: --->", pappassword);

在String原型中定义了两个函数来代替pack函数的使用:

.pad( 32, string ) 用于用空值填充字符串以提供与pack( 'a32', string ) 相同的结果。虽然这里不需要它,但如果想用空值以外的字符填充字符串,它也需要第二个参数。

.packHex 相当于pack( 'H*' ,string ) 并将每对十六进制字符的代码翻译成一个字符。理想情况下,该函数需要更多验证来测试字符串是否为有效的十六进制字符串。

定义输入后,接下来的四行将使用这些函数而不是 pack 设置变量。

由于 Javascript 本身无法对字符串进行异或运算,因此您需要使用循环来提取每个字符,将其转换为数字,对这些值进行异或运算,然后将结果转换回字符以创建 pappassword 字符串。

对我来说,这将返回:

Response: -->  – "fbfd42ffde05fcf8dbdd02b7e8ae2d90"
New Password: --> – "password������������������������"
Pap Password: ---> – "dcbdacb03f5d38ca33c128b931c272a"

这是一个结果,可惜与PHP代码不同。

这是因为我安装的 PHP 配置为在内部使用 ISO-8859-1 编码,而 Javascript 本身使用 UTF-16。

这在正常使用中不是问题,但这意味着各个 md5 函数将看到不同的值,因此返回不同的哈希值。

假设您正在使用 PHP 后端编写身份验证例程,您显然需要一致的结果。可能有模块可用于转换 JavaScript 值的编码以实现兼容性,但更改 PHP 代码要容易得多。

因为我们知道十六进制字符串将是一个字节,所以 Javascript 有效地使用 UTF-8,因此 PHP 可以通过使用 utf8_encode() 函数在对打包的十六进制字符串进行 md5 处理之前对其进行转换。删除>

最初我认为 Javascript 正在内部将编码的十六进制字符转换为它们的 unicode 等价物,但事实并非如此。相反,是 Javascript 中使用的 md5 模块对输入执行 UTF-8 转换。

这留下了两个可能的选择。

1.使用 UTF-8 PHP

如果可能,您可以重新配置您的 PHP 服务器以使用 UTF-8 编码。或者您可以更改您的脚本以使用 utf8_encode() 函数来反映与 Javascript 中发生的相同过程,并将十六进制压缩字符串转换为 UTF-8,然后再将它们传递给 md5()

$challenge = 'c731395aca5dcf45446c0ae83db5319e';
$uamsecret = 'secret';
$password = 'password';

$hexchal = pack ("H32", $challenge);
$newchal = pack ("H*", md5(utf8_encode($hexchal) . $uamsecret));
$response = md5("\0" . $password . utf8_encode($newchal));
$newpwd = pack("a32", $password);
$pappassword = implode ("", unpack("H32", ($newpwd ^ $newchal)));

echo "Response: ---> ", $response, "\n";
echo "New Password: ---> ", $newpwd, "\n";
echo "Pap Password: ---> ", $pappassword, "\n";

然后返回与 JavaScript 相同的结果:

Response: ---> fbfd42ffde05fcf8dbdd02b7e8ae2d90
New Password: ---> password
Pap Password: ---> dcbdacb03f5d38ca33c128b9310c272a

2.在 Javascript 中更改 md5 模块

我假设您正在使用 bluimp JavaScript-MD5 模块,因为这是您链接的 DaloRADIUS 例程使用的模块。您可以对此进行修补以绕过 UTF-8 转换。

有多种方法可以修补此问题,但第 259 行是 md5() 函数本身的定义。这只是一组if 语句,用于解析输入选项并调用相应的内部函数。

但是,此块调用的函数只是提供 UTF-8 输入转换,通过一个名为 str2rstrUTF8() 的函数,然后调用相应的函数来提供实际的散列。因此,您可能需要修补 md5() 以接受第三个参数,以指示是否应应用 UTF-8 转换,然后酌情调用其他函数。

但是,简单地完全删除转换,更简单的方法是更改​​ str2rstrUTF8() 以返回未更改的输入。这个函数可以在第 239 行找到,将其更改为如下所示将停止转换:

function str2rstrUTF8 (input) 
  return input

或者,要删除多余的函数调用,您可以只删除对它的引用。将第 246 行开始的函数改为如下:

function rawMD5 (s) 
  return rstrMD5(s)

第 252 行上的 rawHMACMD5() 函数还包括对 str2rstrUTF8() 函数的调用,您可能还需要对其进行修补以保持一致性,但这不是上述例程所必需的。当传递第二个参数以提供 key hash 时,将调用该函数,而原生 PHP md5() 函数中没有此功能。

在进行这些更改后,Javascript 例程现在返回与您的原始(使用 ISO-8859-1)PHP 代码相同的输出:

Response: -->  – "2d4bd27184f5eb032641137f728c6043"
New Password: --> – "password������������������������"
Pap Password: ---> – "356a1fb08f909fc40dfe448fc483ce3"

【讨论】:

有没有办法将我的 Javascript 配置为使用 ISO-8859-1 编码,而不是将 PHP 更改为使用 UTF? pappassword 被发送到运行 chilispot 软件的网络访问服务器(路由器),该服务器期望来自未更改的 PHP 的准确响应。因此,有没有办法更改 Javascript 上的编码以匹配确切的响应? 是的!我现在没有时间更新答案,但会在早上做。基本上,虽然我认为是 Javascript 将打包的十六进制字符串转换为 UTF,但实际上它只留下了字符代码,而是 md5 模块将其输入字符串转换为 UTF。这很容易修补以避免该步骤,并且在这样做之后它返回与原始 PHP 示例相同的结果。如果您想自己寻找,需要跳过的是 md5.js 中的 str2rstrUTF8 函数,否则我明天会发布完整的详细信息。 我会看看str2rstrUTF8。感谢您指出。当您有时间时,也会感谢您在答案中提供。 已更新,希望答案有意义。打补丁非常简单,但有多种应用方法,这取决于个人喜好。 @Michael 这太好了,我已经为你打开了另一个赏金。但我可以在 23 小时后奖励这个..

以上是关于Javascript Mismatch 中的 PHP Pack/Unpack 实现的主要内容,如果未能解决你的问题,请参考以下文章

本地主机中的字体有时“加载资源失败:net::ERR_CONTENT_LENGTH_MISMATCH”

错误:MVC 应用程序中的 Google 登录中的 redirect_uri_mismatch

javascript 问题381:yyyymmdd - no72。メニューページ回游施策_ph2

javascript Issue371:20180419 - no70。メニューページ回游施策_ph1

javascript Issue10:20180611 - no78。メニューページ回游施策_ph1(再)

redirect_uri_mismatch 请求中的重定向 URI 与授权给 OAuth 客户端的重定向 URI 不匹配