Skip to content

Commit

Permalink
实现支付宝公钥证书签名和验签 #16
Browse files Browse the repository at this point in the history
  • Loading branch information
Yurunsoft committed Apr 17, 2020
1 parent c17b0bd commit 8bde271
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 1 deletion.
28 changes: 28 additions & 0 deletions src/AlipayApp/Params/PublicParams.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,34 @@ class PublicParams extends PublicBase
*/
public $aesKey;

/**
* 是否使用公钥证书模式
*
* @var boolean
*/
public $usePublicKeyCert = false;

/**
* 支付宝公钥证书文件路径
*
* @var string
*/
public $alipayCertPath;

/**
* 支付宝根证书文件路径
*
* @var string
*/
public $alipayRootCertPath;

/**
* 支付宝应用公钥证书文件路径
*
* @var string
*/
public $merchantCertPath;

public function __construct()
{
$this->apiDomain = 'https://openapi.alipay.com/gateway.do';
Expand Down
51 changes: 50 additions & 1 deletion src/AlipayApp/SDK.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace Yurun\PaySDK\AlipayApp;

use \Yurun\PaySDK\Base;
use Yurun\PaySDK\Lib\CertUtil;
use \Yurun\PaySDK\Lib\Encrypt\AES;
use \Yurun\PaySDK\Lib\ObjectToArray;

Expand All @@ -16,6 +17,43 @@ class SDK extends Base
*/
public $publicParams;

/**
* 支付宝公钥证书SN
*
* @var string
*/
public $alipayCertSn;

/**
* 应用公钥证书SN
*
* @var string
*/
public $appCertSn;

/**
* 支付宝根证书SN
*
* @var string
*/
public $alipayRootCertSn;

/**
* __construct
*
* @param \Yurun\PaySDK\AlipayApp\Params\PublicParams $publicParams
*/
public function __construct($publicParams)
{
parent::__construct($publicParams);
if($publicParams->usePublicKeyCert)
{
$this->alipayCertSn = CertUtil::getCertSN($publicParams->alipayCertPath);
$this->appCertSn = CertUtil::getCertSN($publicParams->merchantCertPath);
$this->alipayRootCertSn = CertUtil::getRootCertSN($publicParams->alipayRootCertPath);
}
}

/**
* 处理执行接口的数据
* @param $params
Expand All @@ -27,13 +65,19 @@ class SDK extends Base
public function __parseExecuteData($params, &$data, &$requestData, &$url)
{
$data = \array_merge(ObjectToArray::parse($this->publicParams), ObjectToArray::parse($params));
unset($data['apiDomain'], $data['appID'], $data['businessParams'], $data['appPrivateKey'], $data['appPrivateKeyFile'], $data['appPublicKey'], $data['appPublicKeyFile'], $data['_syncResponseName'], $data['_method'], $data['_isSyncVerify'], $data['aesKey'], $data['isUseAES']);
unset($data['apiDomain'], $data['appID'], $data['businessParams'], $data['appPrivateKey'], $data['appPrivateKeyFile'], $data['appPublicKey'], $data['appPublicKeyFile'], $data['_syncResponseName'], $data['_method'], $data['_isSyncVerify'], $data['aesKey'], $data['isUseAES'], $data['alipayCertPath'], $data['alipayRootCertPath'], $data['merchantCertPath'], $data['usePublicKeyCert']);
$data['app_id'] = $this->publicParams->appID;
$data['biz_content'] = $params->businessParams->toString();
if($this->publicParams->isUseAES)
{
$data['biz_content'] = AES::encrypt($data['biz_content'], \base64_decode($this->publicParams->aesKey));
}
// 公钥证书参数
if($this->publicParams->usePublicKeyCert)
{
$data['app_cert_sn'] = $this->appCertSn;
$data['alipay_root_cert_sn'] = $this->alipayRootCertSn;
}
$data['timestamp'] = date('Y-m-d H:i:s');
$data['sign'] = $this->sign($data);
$requestData = $data;
Expand Down Expand Up @@ -119,6 +163,11 @@ public function verifySync($params, $data)
{
return true;
}
// 公钥证书验证
if($this->publicParams->usePublicKeyCert && $this->alipayCertSn !== (isset($data['alipay_cert_sn']) ? $data['alipay_cert_sn'] : null))
{
return false;
}
$content = \json_encode($data[$params->_syncResponseName], JSON_UNESCAPED_UNICODE);
if(empty($this->publicParams->appPublicKeyFile))
{
Expand Down
91 changes: 91 additions & 0 deletions src/Lib/CertUtil.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php
namespace Yurun\PaySDK\Lib;

/**
* 证书工具类
*
* @source https://github.com/alipay/alipay-easysdk/blob/master/php/src/Kernel/Util/AntCertificationUtil.php 算法代码来自支付宝官方sdk
*/
class CertUtil
{
/**
* 从证书中提取序列号
* @param $certPath
* @return string
*/
public static function getCertSN($certPath)
{
$cert = file_get_contents($certPath);
$ssl = openssl_x509_parse($cert);
$SN = md5(static::array2string(array_reverse($ssl['issuer'])) . $ssl['serialNumber']);
return $SN;
}

/**
* 提取根证书序列号
* @param $certPath string 根证书
* @return string|null
*/
public static function getRootCertSN($certPath)
{
$cert = file_get_contents($certPath);
$array = explode('-----END CERTIFICATE-----', $cert);
$SN = null;
for ($i = 0; $i < count($array) - 1; $i++)
{
$ssl[$i] = openssl_x509_parse($array[$i] . '-----END CERTIFICATE-----');
if (strpos($ssl[$i]['serialNumber'], '0x') === 0)
{
$ssl[$i]['serialNumber'] = static::hex2dec($ssl[$i]['serialNumber']);
}
if ($ssl[$i]['signatureTypeLN'] == 'sha1WithRSAEncryption' || $ssl[$i]['signatureTypeLN'] == 'sha256WithRSAEncryption')
{
if ($SN == null)
{
$SN = md5(static::array2string(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
}
else
{
$SN = $SN . '_' . md5(static::array2string(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
}
}
}
return $SN;
}

/**
* 数组转字符串
*
* @param array $array
* @return string
*/
public static function array2string($array)
{
$string = [];
if ($array && is_array($array))
{
foreach ($array as $key => $value)
{
$string[] = $key . '=' . $value;
}
}
return implode(',', $string);
}

/**
* 0x转高精度数字
* @param $hex
* @return int|string
*/
public static function hex2dec($hex)
{
$dec = 0;
$len = strlen($hex);
for ($i = 1; $i <= $len; $i++)
{
$dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
}
return $dec;
}

}
7 changes: 7 additions & 0 deletions test/AlipayApp/ftf_pay.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
$params->appID = $GLOBALS['PAY_CONFIG']['appid'];
$params->appPrivateKey = $GLOBALS['PAY_CONFIG']['privateKey'];
$params->appPublicKey = $GLOBALS['PAY_CONFIG']['publicKey'];

// 公钥证书演示
$params->usePublicKeyCert = true;
$params->alipayCertPath = __DIR__ . '/cert/alipayCertPublicKey_RSA2.crt';
$params->alipayRootCertPath = __DIR__ . '/cert/alipayRootCert.crt';
$params->merchantCertPath = __DIR__ . '/cert/appCertPublicKey_2016073000123475.crt';

// $params->appPrivateKeyFile = ''; // 证书文件,如果设置则这个优先使用
$params->apiDomain = 'https://openapi.alipaydev.com/gateway.do'; // 设为沙箱环境,如正式环境请把这行注释

Expand Down

0 comments on commit 8bde271

Please sign in to comment.