Files
pay/plugins/kuaiqian/inc/CryptoProcessor.php
2025-11-28 10:08:12 +08:00

189 lines
6.8 KiB
PHP

<?php
namespace kuaiqian;
use Exception;
class CryptoProcessor
{
//商户证书
private $merchantCert;
//商户私钥
private $merchantKey;
//快钱证书
private $kuaiqianCert;
private $temp_path = PLUGIN_ROOT.'kuaiqian/temp/';
public function __construct($merchantCertPath, $merchantCertPath_password, $kuaiqianCertPath)
{
$pfx = file_get_contents($merchantCertPath);
if(!openssl_pkcs12_read($pfx, $certs, $merchantCertPath_password)){
throw new Exception("商户证书读取失败!");
}
$this->merchantCert = $certs['cert'];
$this->merchantKey = $certs['pkey'];
$this->kuaiqianCert = file_get_contents($kuaiqianCertPath);
}
/**
* 商户端加密加签
* @param String $originalData 加密前明文
* @param String $salt 盐值,防止并发请求下,加解密txt文件的内容被覆写,可自定义
* @return string 请求快钱的body
*/
public function seal(string $originalData,string $salt){
$Body_final['signedData'] = $this->getSignedData($originalData,$salt);
$Body_final['envelopedData'] = $this->getEnvelopedData($originalData,$salt);
if(0==strlen($Body_final['signedData']) || 0==strlen($Body_final['envelopedData'])){
throw new Exception("请求出错,signedData或envelopedData为空!");
}
return $Body_final;
}
/**
* 商户端解密验签
* @param String $signedData 快钱返回的签名
* @param String $envelopedData 快钱返回的密文
* @param String $salt 盐值,防止并发请求下,加解密txt文件的内容被覆写,可自定义
* @return string 解密后的明文
*/
public function unseal(string $signedData,string $envelopedData,string $salt){
$responseDecryptData = $this->getDecryptData($envelopedData,$salt);
$verifyResult = $this->getVerifyFlag($responseDecryptData,$signedData,$salt);
if(0==strlen($responseDecryptData)){
throw new Exception("客户端解密失败!");
}
if(!$verifyResult){
throw new Exception("客户端验签失败!");
}
return $responseDecryptData;
}
/**
* 获取密文 快钱证书加密
*/
public function getEnvelopedData(string $originalData,string $salt):string {
//定义一个data文件,写入明文body
$originalDataPath = $this->temp_path . 'data_' . $salt . '.txt';
if(!file_put_contents($originalDataPath, $originalData)){
throw new Exception("获取密文失败,写入文件失败!");
}
//获取证书内容
$publickey = $this->kuaiqianCert;
//定义一个endata文件,存放加密后数据
$enDataPath = $this->temp_path . 'endata_' . $salt . '.txt';
openssl_pkcs7_encrypt($originalDataPath,$enDataPath,$publickey,null,
PKCS7_BINARY,OPENSSL_CIPHER_AES_128_CBC);
//获取密文及字符处理
$enData = file_get_contents($enDataPath);
$finalEnData = str_replace(array("\r\n","\r","\n","\\"),"",
substr($enData,191,strlen($enData)));
//返回
unlink($originalDataPath);
unlink($enDataPath);
return $finalEnData;
}
/**获取签名 商户证书签名
* @return Base64string
*/
public function getSignedData(string $originalData,string $salt):string {
$originalDataPath = $this->temp_path . 'origdata_' . $salt . '.txt';
if(!file_put_contents($originalDataPath, $originalData)){
throw new Exception("获取签名失败,写入文件失败!");
}
$signdataPath = $this->temp_path . 'signdata_' . $salt . '.txt';
openssl_pkcs7_sign($originalDataPath,$signdataPath,
$this->merchantCert,
$this->merchantKey,
[],
PKCS7_BINARY);
$signdata = file_get_contents($signdataPath);
$finalsigndata = str_replace(array("\r\n","\r","\n"),array(""),
substr($signdata,186,strlen($signdata)));
unlink($originalDataPath);
unlink($signdataPath);
return $finalsigndata;
}
/**返回解密 商户证书解密
* @return string
*/
public function getDecryptData(string $encryptoData,string $salt):string {
$respdecryptoDataPath = $this->temp_path . 'respDecryptoData_' . $salt . '.txt';
//txt内容须遵守SMIME格式规范,请勿做增删、对齐等操作
$txt ="MIME-Version: 1.0
Content-Disposition: attachment; filename=\"smime.p7m\"
Content-Type: application/x-pkcs7-mime; smime-type=enveloped-data; name=\"smime.p7m\"
Content-Transfer-Encoding: base64"."\n\n\n".$encryptoData;
if(!file_put_contents($respdecryptoDataPath, $txt)){
throw new Exception("返回解密失败,写入文件失败!");
}
$decryptoDataPath = $this->temp_path . 'decryptoData_' . $salt . '.txt';
if(openssl_pkcs7_decrypt($respdecryptoDataPath,$decryptoDataPath,
$this->merchantCert,
$this->merchantKey)){
$decryptoData = file_get_contents($decryptoDataPath);
//Log::info('解密成功!快钱返回明文body为:'.$decryptoData);
unlink($decryptoDataPath);
unlink($respdecryptoDataPath);
return $decryptoData;
}else{
unlink($respdecryptoDataPath);
unlink($decryptoDataPath);
throw new Exception('返回数据解密失败!failed to decrypt!');
}
}
/**返回验签 快钱证书验签
* @return bool
*/
public function getVerifyFlag(string $decryptoData,string $signedData,string $salt):bool {
$respsignedDataPath = $this->temp_path . 'respSignedData_' . $salt . '.txt';
$txt =$signedData;
file_put_contents($respsignedDataPath,$this->formatSmimeSignData($txt,$decryptoData));
$unSignDataPath = $this->temp_path . 'unSignData_' . $salt . '.txt';
$flag = openssl_pkcs7_verify($respsignedDataPath,PKCS7_NOVERIFY,$unSignDataPath);
unlink($respsignedDataPath);
unlink($unSignDataPath);
return $flag == 1;
}
/**
* @return String $signData 内容须遵守SMIME格式规范,请勿做增删、对齐等操作
*/
public function formatSmimeSignData($txt,$decryptoData)
{
$signData = chunk_split($txt, 76, "\n");
$boundary = "----" . md5($signData);
$signData = <<<EOD
MIME-Version: 1.0
Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg=sha256; boundary="$boundary"
This is an S/MIME signed message
--$boundary
$decryptoData
--$boundary
Content-Type: application/x-pkcs7-signature; name="smime.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
$signData
--$boundary--
EOD;
return $signData;
}
}