我的php脚本未解密Sagepay Form 3.00

时间:2018-08-27 10:24:47

标签: php encryption sagepay

我已将网站移至新的托管服务提供商,在那里我收到加密响应的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)问题也发布在这里:请参见Website to Sagepay submit encryption code was working but now fails after server php upgrade

任何人都可以建议这次这次失败的原因以及我该如何解决吗?

编辑14/12/18 经过调查后的更多信息,此外,我还提供了更多解释和来自两个相关脚本的完整代码--------------- ---------

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

我现在发现,如果我在“ completed.php”页面(Sagepay响应所指向的URL)上删除此行(以下),则脚本不会挂起;但是,因为那条线会导致致命错误。

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

没有代码行和由此产生的错误,该scipt可以继续前进并调用以下页面(“ return.php”),该页面随后将付款结果信息显示给客户,并执行其他操作(例如,发送全额付款)订单详细信息发送到我们本地的-不在Internet上-数据库)。

但是,删除了该行后,URL中的加密不被处理,因此结果变量中没有值,completed.php页将这些值转发到return.php页。

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

缺少“成功”状态值还意味着Web mysql数据库中的订单未标记为已确认。

我尝试了该产品线的许多其他变体,但均无济于事(尽管此处给出的变体在网站移至新主机之前有效)。

该行当然会在functions.php文件中的类“ DHclassInFunc”中调用该函数。

我将两个文件(completed.php和functions.php)的活动代码放在下面

据我所知,根本问题是线路

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

在'crypt'中不接收任何值,因此没有字符串供function.php解密例程使用,从而在调用该函数时导致致命错误:“ PHP致命错误:未找到类'SagepayApiException'在/ 208第208行的/redacted/redacted/redacted.com/www/redacted/protx/functions.php中

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

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

为了尝试显示传递给函数的值,但是它简单地显示var的名称,而不是接收到completed.php页面地址栏中来自url内容的值Sagepay的回复-例如:

https://www.redacted.com/redacted/protx/completed.php?crypt=@ad6721a09c786829cd839586df0fe047ea0f0e9c791ddfe5d55b7175881aa4609ccfb4768a8b84dd9f259614d0edf0f03254a1967279693509e72190c8248cd56d1cefa713592f84eca4e8d7477ac89c9dd783b350a21766500c1c91fde3dbe5deb7887bea0e5c07e58274dec93224729f265730a4aecf5cf9c7216dad2b5eecc4d128e6c8389c1c9d5d297b7a10ccb53e37eae5b7a996a308c10f2d0edc0b41b6b38c6e56375a6421d110a0a3fe40cdfa2daa2fa6e0bf767204d209aa300d9f907ea686ee9a9dcc0992c14c325123ab53d7885bc6dc66eebf3c341002034fbce6277ccc6fbb8734c3cdab58dcd294d0a3a4430c7b091beed81fd97cadbf24b9149f9541e5d8e8c45a4e267fc0d14222c45963fe847ec12a9fedf05eba2a78caf769825046584b112d353d92d38aedc3cb086fc0c8250e20ef975dc377438b7c3a34c96cacba9ed1670b2af1bcd0945a5a0424c0532f23b0a6662db8198a2368d60ee3785f07826005593292154abe06abf55ff1d461b714e1fb53b5da3db1f21eb6b01169a2cf78d872de5ac96e41e088a7bf1e6f88aa8cc5c6b4bfd5d82f63

关于是否可能是unicode / iso问题,我看不到为什么这会导致$ strIn中有一个空值,因为这还没有被处理,只能被捕获(或不被捕获)。

COMPLETED.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";
?>

FUNCTIONS.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);
}


?>

我非常需要帮助来解决代码方面的问题,或者是否在尝试公开看似空字符串的值方面犯了错误,因此跳到了错误的结论。

编辑星期二18/12/18 ---------------

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

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

不幸的是,我现在对理解调试输出完全不知所措-因为首先,尽管寻求帮助,但我一点也不了解解密功能。

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

$hex = substr($strIn, 1);

在“ functions.php”中,它会在剥离“ @”后产生传入加密的内容。

但是一旦脚本移至第

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

这是错误的,因为如果变量内容现在的输出乱码“乱码”。我不了解“包装”的工作原理,但我认为所有字符都应保持可读性,因此这是一个编码问题。

Link to image of a screenshot of the characters

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

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中提交?考虑到编码似乎常常不“坚持”,这是影响编码的Web技术的战场,无论如何,无论如何,仅为了Sagepay最好地强制采用此ISO。

另一方面,如果仅仅是“ pack”功能没有用的话,那就太好了;以及是否有简单的方法或位置来解决此问题。谁能建议。


1 个答案:

答案 0 :(得分:2)

在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,而无需任何转换。