我的php脚本不会解密Sagepay表单3.00版本中的返回密码



我已经将我的网站转移到一个新的托管提供商,在那里我的Sagepay Form v3脚本(接收加密响应)现在失败了。

在以前的托管提供商,脚本正在运行(php版本为5.5.9),新的托管提供了5.4到6的选择。在第一家托管提供商那里,php版本在很长一段时间前都是5.2(或者可能是5.3),当他们最终强制更改为5.5时,我的网站脚本中的很多东西都被破坏了,这导致了我很难修复它们,我最终实现了这一点。

其中一件事是解密失败了,就像现在再次解密一样。在这种情况下,我最终通过将解密行从更改来修复它

$Decoded = DHclassInFunc::decryptAes($crypt,$EncryptionPassword);

至:

$Decoded = DHclassInFunc::decryptAes(($_GET['crypt']),$EncryptionPassword);

我试过许多其他的变体,但只有最后一个有效。

所以现在问题又回来了,我完全不知所措。我试过以前的所有变体,但都不起作用。还有我的新主机上提供的各种php版本。

我之前的(LONG)问题也发布在这里:请参阅网站向Sagepay提交加密代码,但现在在服务器php升级后失败

有人能告诉我为什么这次失败了,以及我能做些什么来解决它吗?

2018年12月14日编辑调查后的更多信息加上我将包括更多解释和两个相关脚本的完整代码-----------------------

我没有取得任何进展,在Sagepay退货不起作用的情况下,网站订单不得不手动管理。现在我有一点时间,所以我要再试一次。

我现在发现,如果我删除"completed.php"页面(Sagepay响应指向的url)上的这一行(下面),脚本就不会挂起;然而,这是因为正是这条线导致了致命的错误。

$Decoded = DHclassInFunc::decryptAes(($_GET['crypt']),$EncryptionPassword);

在没有这行和由此产生的错误的情况下,scipt可以继续前进并调用下面的页面("return.php"),然后向客户显示付款结果信息,还可以执行其他操作(例如将完整的订单详细信息发送到我们的本地数据库,而不是互联网数据库)。

然而,删除该行后,url中的crypt不会被处理,因此在completed.php页面转发到return.php页面的结果变量中没有值。

这意味着$status变量为空;在return.php页面中,这被评估为一个错误,因此会向客户显示一条消息,说有一个错误并且没有付款,这是不正确的。

缺少"成功"状态值也意味着web mysql数据库中的订单没有被标记为已确认。

我尝试了许多其他变体的线路,但都没有成功(尽管在网站被转移到新主机之前,这里给出的那个有效)。

这一行当然会调用functions.php文件中的类"DHclassInFunc"中的函数。

我在下面附上了两个文件的活动代码,completed.php和functions.php

就我所能解决的根本问题是

$Decoded = DHclassInFunc::decryptAes(($_GET['crypt']),$EncryptionPassword);

在"crypt"中没有接收到任何值,因此没有可供函数处理的字符串。php解密例程在调用该函数时会导致致命错误:"php致命错误:在第208行的/redecated/redecated/Redecated.com/www/redated/protx/functions.php中找不到类‘SagepayApiException’">

我在functions.php代码中添加了一行,如下所示:

echo '$strIn' . "  string in with @ should be here?";

为了尝试公开传递给函数的值,但它只打印var的名称,而不是当它收到Sagepay的响应时,在已完成的.php页面的地址栏中的url内容中的值-例如:

https://www.redacted.com/redacted/protx/completed.php?crypt=@ad6721a09c786829cd839586df0f047ea0e9c791ddfe5d55b7175881a460ccfb4768a8b84dd9f259614a1df0f03254a1967279693509e72190c8248cd56d1cefa713592f84eca4e8d7477ac89c9dd783b350a21766500c1c91fde3dbe5deb7887bea0e07e8274dec93224729f265730a4aecf5cf9c7216dad2b5ecc4d128e6c8389c1c9d5d297b7a10ccb53e37eae5b7a996a3 08c10f20edc0b41b6b38c6e56375a6421d110a0a3fe40cdfa2daa2fa6e0bf767204d209aa300d9f907ea686ee9a9dcc0992c14c325123ab53d7885bc6dc66eebf3c341002034fbce6277ccc6fbb8734c3cdab58dcd294d0a3a4430c7b091已被81fd97cadbf24b9149f9541e5d8e8c45a47fc0d14222c45963e847ec12a9fedf05eba2a78caf769825046584b112d353d92d38aedc3cb086fc0c8250e20ef975dc377438b7c3a34c96cacba9ed1670b2af1bcd0945a5a0424c0532f23b0662db819 8a2368d60ee3785f07826005593292154abe06abf55ff1d461b714e1fb53b5da3db1f21eb6b01169a2cf78d872de5ac96e41e088a7bf1e6f88aa8cc5c6b4bfd5d82f63

关于这是否是unicode/iso问题,我不明白为什么这会导致$strIn中的值为空,因为这根本没有被处理,只是被捕获(或者没有?)。

已完成.php--------------------

<?php
include "functions.php";
$Decoded = DHclassInFunc::decryptAes($_GET['crypt'],$EncryptionPassword); 
$values = getToken($Decoded);
$VendorTxCode = $values['VendorTxCode'];
$Status = $values['Status'];
$VPSTxID = $values['VPSTxId'];
$TxAuthNo = $values['TxAuthNo'];
$AVSCV2 = $values['AVSCV2'];
$Amount = $values['Amount'];
// protocol 2.22 fields
$AddressResult = $values[ 'AddressResult' ];
$PostCodeResult = $values[ 'PostCodeResult' ];
$CV2Result = $values[ 'CV2Result' ];
$GiftAid = $values[ 'GiftAid' ];
$VBVSecureStatus = $values[ '3DSecureStatus' ];
$CAVV = $values[ 'CAVV' ];
// DH my all-in-one details var
$ResultDetails = $ResultDetails . "Vendor Code: " . $VendorTxCode . " - "; 
$ResultDetails = $ResultDetails . "Status: " . $Status . " - "; 
$ResultDetails = $ResultDetails . "VPS Transaction ID: " . $VPSTxID . " - ";                                                                                                                                                                                                         
$ResultDetails = $ResultDetails . "Auth Num: " . $TxAuthNo . " - "; 
$ResultDetails = $ResultDetails . "AVS / CV2 response: " . $TxAuthNo . " - "; 
$ResultDetails = $ResultDetails . "Amount: " . $Amount . " - ";         
$ResultDetails = $ResultDetails . "Address Result: " . $AddressResult . " - ";  
$ResultDetails = $ResultDetails . "PostCode Result: " . $PostCodeResult . " - ";   
$ResultDetails = $ResultDetails . "PostCode Result: " . $PostCodeResult . " - ";    
$ResultDetails = $ResultDetails . "CV2 Result: " . $CV2Result . " - ";  
$ResultDetails = $ResultDetails . "GiftAid Result: " . $GiftAid . " - ";     
$ResultDetails = $ResultDetails . "3DSecure Status: " . $VBVSecureStatus . " - "; 
$ResultDetails = $ResultDetails . "CAVV Result: " . $CAVV . " - ";  
$FindHyphen = strpos($VendorTxCode,'-');
$LastIdChar = $FindHyphen;
$MyOrderID = substr($VendorTxCode,0,$LastIdChar);
$StatusSave = $Status;
echo '  <FORM METHOD="POST" FORM NAME="GoToReturn" ACTION="../MXKart/return.php">'."n";
echo ' <input type="hidden" name="response_code" value= "';
echo $Status;
echo '">'."n";
echo ' <input type="hidden" name="order_number" value= "';
echo $MyOrderID;
echo '">'."n";
echo ' <input type="hidden" name="secretword" value= "';
echo $secret_word;
echo '">'."n";
//echo addslashes($ResultDetails);
echo ' <input type="hidden" name="response_reason_text" value= "';
echo $ResultDetails;
echo '">'."n";
echo ' <input type="hidden" name="amount" value= "';
echo $Amount;
echo '">'."n";
echo ' <input type="hidden" name="force" value= "';
echo $VendorTxCode;
echo '">'."n";
$msg = "<br><strong>Getting payment result.... </strong> <br><br><h2 style="color:green;">PLEASE WAIT AT THIS PAGE - do not close the page or move on. <br>There can be a delay of up to a minute so please be patient.</h2>";
echo $msg."n"; 
echo '</FORM>'."n";
echo '<script language="javascript">'."n";
echo 'document.forms[0].submit();'."n";
echo '</script>'."n";
?>

FUNCTION.php------------------

<?
$VendorName="redacted";
$EncryptionPassword="redacted"; //   LIVE  server destination
//************ NEW CRYPT STUFF COPIED FRON SAGEPAY KIT util.php
//DH added class definition as shown in stackoverflow page - trying to fix error when run, on line static private function etc
class DHclassInFunc{
/**
* PHP's mcrypt does not have built in PKCS5 Padding, so we use this.
*
* @param string $input The input string.
*
* @return string The string with padding.
*/
static protected function addPKCS5Padding($input)
{
$blockSize = 16;
$padd = "";
// Pad input to an even block size boundary.
$length = $blockSize - (strlen($input) % $blockSize);
for ($i = 1; $i <= $length; $i++)
{
$padd .= chr($length);
}
return $input . $padd;
}

/**
* Remove PKCS5 Padding from a string.
*
* @param string $input The decrypted string.
*
* @return string String without the padding.
* @throws SagepayApiException
*/
static protected function removePKCS5Padding($input)
{
$blockSize = 16;
$padChar = ord($input[strlen($input) - 1]);
/* Check for PadChar is less then Block size */
if ($padChar > $blockSize)
{
throw new SagepayApiException('Invalid encryption string');
}
/* Check by padding by character mask */
if (strspn($input, chr($padChar), strlen($input) - $padChar) != $padChar)
{
throw new SagepayApiException('Invalid encryption string');
}
$unpadded = substr($input, 0, (-1) * $padChar);
/* Chech result for printable characters */
if (preg_match('/[[:^print:]]/', $unpadded))
{
throw new SagepayApiException('Invalid encryption string');
}
return $unpadded;
}

/**
* Encrypt a string ready to send to SagePay using encryption key.
*
* @param  string  $string  The unencrypyted string.
* @param  string  $key     The encryption key.
*
* @return string The encrypted string.
*/
static public function encryptAes($string, $key)
{
// AES encryption, CBC blocking with PKCS5 padding then HEX encoding.
// Add PKCS5 padding to the text to be encypted.
$string = self::addPKCS5Padding($string);
// Perform encryption with PHP's MCRYPT module.
$crypt = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $string, MCRYPT_MODE_CBC, $key);
// Perform hex encoding and return.
return "@" . strtoupper(bin2hex($crypt));
}
/**
* Decode a returned string from SagePay.
*
* @param string $strIn         The encrypted String.
* @param string $password      The encyption password used to encrypt the string.
*
* @return string The unecrypted string.
* @throws SagepayApiException
*/
static public function decryptAes($strIn, $password)
{
echo '$strIn' . "  string in with @ should be here?";
$strIn = htmlspecialchars($strIn, ENT_COMPAT,'utf-8', true);
// HEX decoding then AES decryption, CBC blocking with PKCS5 padding.
// Use initialization vector (IV) set from $str_encryption_password.
$strInitVector = $password;
// Remove the first char which is @ to flag this is AES encrypted and HEX decoding.
$hex = substr($strIn, 1);
// Throw exception if string is malformed
if (!preg_match('/^[0-9a-fA-F]+$/', $hex))
{
//DH added section to print result of decryption onto page for debugging
//$hex = "pseudo hex";
//echo "throw error at line 188";
// echo $hex;
throw new SagepayApiException('Invalid encryption string');
}
$strIn = pack('H*', $hex);
// Perform decryption with PHP's MCRYPT module.
$stringReturn = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $password, $strIn, MCRYPT_MODE_CBC, $strInitVector);
return self::removePKCS5Padding($string);
}
}
/* The getToken function.                                                                                         **
** NOTE: A function of convenience that extracts the value from the "name=value&name2=value2..." VSP reply string **
**     Works even if one of the values is a URL containing the & or = signs.                                      */

function getToken($thisString) {
// List the possible tokens
$Tokens = array(
"Status",
"StatusDetail",
"VendorTxCode",
"VPSTxId",
"TxAuthNo",
"Amount",
"AVSCV2", 
"AddressResult", 
"PostCodeResult", 
"CV2Result", 
"GiftAid", 
"3DSecureStatus", 
"CAVV" );
// Initialise arrays
$output = array();
$resultArray = array();
// Get the next token in the sequence
for ($i = count($Tokens)-1; $i >= 0 ; $i--){
// Find the position in the string
$start = strpos($thisString, $Tokens[$i]);
// If it's present
if ($start !== false){
// Record position and token name
$resultArray[$i]->start = $start;
$resultArray[$i]->token = $Tokens[$i];
}
}
// Sort in order of position
sort($resultArray);
// Go through the result array, getting the token values
for ($i = 0; $i<count($resultArray); $i++){
// Get the start point of the value
$valueStart = $resultArray[$i]->start + strlen($resultArray[$i]->token) + 1;
// Get the length of the value
if ($i==(count($resultArray)-1)) {
$output[$resultArray[$i]->token] = substr($thisString, $valueStart);
} else {
$valueLength = $resultArray[$i+1]->start - $resultArray[$i]->start - strlen($resultArray[$i]->token) - 2;
$output[$resultArray[$i]->token] = substr($thisString, $valueStart, $valueLength);
}      
}
// Return the ouput array
return $output;
}
// Randomise based on time
function randomise() {
list($usec, $sec) = explode(' ', microtime());
return (float) $sec + ((float) $usec * 100000);
}

?>

我非常需要帮助来解决代码方面的问题,或者我是否在尝试公开看似空的字符串的值时出错,从而得出错误的结论。

2018年12月18日星期二编辑---------------

我已经取得了一些进展,因为我发现了页面"completed.php"中的$_GET在Sagepay发送的返回回复中根本没有从页面url中获得任何值的原因。

这是因为托管平台的默认php服务器设置只接受url中最多512个字符;我能够将其更改为2000个字符(见后面的评论),这解决了部分问题;致命错误已经消失,但解密仍然失败。但是,我现在可以调试了,因为函数现在有数据可以使用,并且我可以跟踪脚本不同部分的值。

不幸的是,我现在完全无法理解调试输出,因为尽管我在寻求帮助,但首先我根本不理解解密函数。

就解密线路而言,输出似乎是合理的

$hex = substr($strIn, 1);

在"functions.php"中,它在"@"被剥离后生成传入密码的内容。

但一旦脚本移动到行

$strIn = pack('H*', $hex);

它出错是因为变量内容的输出现在到处都是"垃圾"字符。我不明白"pack"是如何工作的,但我认为所有字符都应该保持可读性,因此这是一个编码问题。

链接到字符屏幕截图的图像

在上面链接的图像中,显示为黑色菱形内问号的字符似乎就是其中的一些-

e?g!xh)̓G]/|CՖ'#]Ws܀õY?Ig@uQ*ߎ@KѦ

当我用快速选择和复制捕获文本,然后传递到文本编辑器时。

但我不知道垃圾字符是否仅限于"pack"函数插入的字符,因此编码不匹配仅限于函数,而不是Sagepay之间提交和返回数据的整体编码问题。

不幸的是,自从网站迁移到新主机后,经过漫长的编码混乱(尝试一切)过程,我非常困惑,将脚本、标头、显式编码语句、脚本文件编码、php.ini编码声明、Mysql数据库编码等从旧的(主要)ISO改为utf_8。大多数情况下,只是试图消除用户在网站上实际可见的异常字符。一只鱿鱼,你就会得到一只不同的鱿鱼。

因此,现在一想到如果这纯粹是一个编码问题,我的头就会悸动。Sagepay告诉我,Form 3与unicode兼容,但我知道这与我收到的其他建议相矛盾,事实上,我自己的经验是,在我以前的托管提供商那里,我经历过php和Sagepay版本的更改。

网站和数据库的基本原理不可能改回ISO,但如果我不得不以某种方式让Sagepay单独使用ISO,我该如何最容易地做到这一点?

提交给Sagepay在utf-8下运行良好,但在我可以指定退货的ISO之前,我是否必须将其更改为以ISO提交,这才是真正的问题所在?考虑到编码似乎往往不会"坚持",这是影响编码的网络技术的战场,如何最好地强制执行ISO。

另一方面,如果只是"打包"功能不正常,那就太好了;如果有一个简单的方法或地方来解决这个问题。有人能给我建议吗。


在Omnipay Sage Pay驱动程序(Omnipay Common v3.x)中实现https://github.com/thephpleague/omnipay-sagepay/blob/master/src/Message/Form/CompleteAuthorizeRequest.php#L47

$crypt = $_GET['crypt'];
// Remove the leading '@' and decrypt the remainder into a query string.
$hexString = substr($crypt, 1);
// Last minute check to make sure we have data that looks sensible.
if (! preg_match('/^[0-9a-f]+$/i', $hexString)) {
throw new Exception('Invalid "crypt" parameter; not hexadecimal');
}
// Decrypt the crypt string.
$queryString = openssl_decrypt(
hex2bin($hexString),
'aes-128-cbc',
$yourEncryptionKey,
OPENSSL_RAW_DATA,
$yourEncryptionKey
);
// Parse ...&VPSTxId={AE43BAA6-52FF-0C30-635B-2D5E13B75ACE}&...
// into an array of values.
parse_str($queryString, $data);
var_dump($data);
/*
array(17) {
["VendorTxCode"]=>
string(19) "your-original-unique-id"
["VPSTxId"]=>
string(38) "{AE43BAA6-52FF-0C30-635B-2D5E13B75ACE}"
["Status"]=>
string(2) "OK"
["StatusDetail"]=>
string(40) "0000 : The Authorisation was Successful."
["TxAuthNo"]=>
string(6) "376048"
["AVSCV2"]=>
string(24) "SECURITY CODE MATCH ONLY"
["AddressResult"]=>
string(10) "NOTMATCHED"
["PostCodeResult"]=>
string(10) "NOTMATCHED"
["CV2Result"]=>
string(7) "MATCHED"
["GiftAid"]=>
string(1) "0"
["3DSecureStatus"]=>
string(10) "NOTCHECKED"
["CardType"]=>
string(4) "VISA"
["Last4Digits"]=>
string(4) "0006"
["DeclineCode"]=>
string(2) "00"
["ExpiryDate"]=>
string(4) "1220"
["Amount"]=>
string(5) "99.99"
["BankAuthCode"]=>
string(6) "999777"
}
*/

PHP 7不再支持官方Sage Pay库(以及许多基于旧代码的插件)使用的旧加密/解密功能。请改用openssl函数。

$data中返回的所有内容都将是ASCII(它将只返回定义良好的ID和代码,而不返回用户输入的数据)。我不相信它会包含任何扩展的ASCII字符,所以如果需要,可以将其视为UTF-8,而不进行任何转换。

最新更新