Javascript Mismatch中的PHP Pack / Unpack实现

时间:2017-01-14 22:39:59

标签: javascript php node.js

根据this question's related answer,我试图整理类似于这个PHP流程的打包/解包解决方案,但是在Nodejs(Javascript)中使用md5bufferpack

以下是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";

以上回应了这些:

Responses PHP

以明文为准:

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);

结果如下:

Response Javascript

在JSON中:

Response Javascript JSON format

以明文:

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

以上所有代码段均可在此处获取:RadiusNES

我对整个过程的理解并不是最好的,并且会欣赏见解以及我出错的地方。

为什么会出现不匹配?

1 个答案:

答案 0 :(得分:15)

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

虽然可能有模块可以执行您想要的操作,但我有自己的函数来解析十六进制字符串。我也喜欢修改并非每个人都同意的原型,但这些原型可以转换为常规功能。

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本身不能xor字符串,所以你需要使用循环来提取每个字符,将其转换为数字,xor这些值,然后将结果转换回字符以创建pappassword字符串。

对我来说,这将会回归:

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

这是一个结果,但不幸的是与PHP代码不同。

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

这在正常使用中不是问题,但这意味着相应的md5函数将看到不同的值,因此返回不同的散列。

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

因为我们知道十六进制字符串将是一个字节,Javascript实际上是使用UTF-8,所以PHP可以通过使用utf8_encode()函数在md5ing之前转换打包的十六进制字符串来做同样的事情。

最初我认为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";

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

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



2。在Javascript中更改md5模块

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

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

但是,此块调用的函数只是通过一个名为str2rstrUTF8()的函数提供UTF-8输入转换,然后调用相应的函数来提供实际的散列。因此,您可能希望修补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"