问题描述:
对接易通银行,他们的服务开放平台是java开发,而我的是php,现在需要php版本的SDK demo 和java交互。对于cer文件和pfx文件根本不懂,不知道是做什么用的,以前也是用rsa加密,使用公钥私钥。而对方直接给的pfx文件。
解决办法:
pfx格式的证书,是商户证书
cer格式的证书,是平台证书
一、pfx格式的证书
pfx文件也可以转为pem格式的证书再操作;也可以使用openssl_pkcs12_read(),openssl_x509_parse()函数操作。
pfx里面包含两部分内容,一个是商户私钥,一个是商户公钥
二、cer格式的证书
在代码中可能用到的和cer证书有关的php函数openssl_pkey_get_public($publickey),openssl_verify($keyValueStr,$sign,$pkeyid,OPENSSL_ALGO_SHA256)等
cer里面包含1部分内容,就是平台公钥
三、这2个证书的意义
pfx证书,一般叫私钥证书(商户证书),在代码的执行流程中它到底起什么作业?回答是:加签
1、商户通过开放平台提供的参数,安装要求组装一个字符串比如:$keyValueStr='key1=value1&key2=value2&key3=value3....&';
2、使用$pfx = get_file_contents('pfx证书路径')读取私钥证书
3、openssl_pkcs12_read($pfx,$result,$ClientPwd); $ClientPwd是证书密钥,银行的开放平台会提供给你的。$result是返回值,返回的是一个数组
4、这个地方只要用到$result数组pkey元素的内容即:$result['pkey']。 $pkey = $result['pkey'];
5、$pkeyid = openssl_pkey_get_private($pkey); //真正的获取私钥,不过返回的数据类型为资源
6、使用获取到的私钥资源id,对$keyValueStr='key1=value1&key2=value2&key3=value3....&';字符串进行加签,当然加签是又对应好几种算法,我们这个平台使用的OPENSSL_ALGO_SHA256算法。
openssl_sign($keyValueStr,$signature,$pkeyid,OPENSSL_ALGO_SHA256); //$signature 为返回的签名串
7、对签名串进行base64_encode($signature);处理 //不是必须的,看平台的约定情况
8、把最终得到的字符串发送给开放平台服务端
完了服务端在做验签等待,这里不描述了。那是服务端的事情,咱们这里重点是说客户端sdk
按照商量的顺序严格执行,我们就会看到pfx证书在执行流程中的作用。那么你可能想知道为什么要用pfx中的私钥对字符串进行加签,对,就是为了安全,防止发送到服务端的数据被篡改。具体服务端如何验证数据是否安全的,那么咱们看看商户客户端又是如何验证服务端返回的数据安全的,其实是一个道理,看完就理解服务端验签过程了
cer证书,一般叫公钥证书(平台证书),在代码的执行流程中它到底起什么作业?回答是:验签
既然商户发给开放平台服务端的数据,服务端不信任要验签,那么商户客户端也是同样的道理,不信任开放平台返回的数据 ,也要对返回的数据验签。cer中的公钥就是这个作用。
openssl x509 -inform der -in xxx.cer -out xxx.pem
1、根据开放平台返回的数据,按照开放平台的约定对,把返回数据组合成$keyValueStr='key1=value1&key2=value2&key3=value3....&';
2、$public_key = file_get_contents($this->publicKeyPath);读取公钥证书内容
3、$pkeyid = openssl_pkey_get_public($publickey); //资源类型
4、$sign = base64_decode($sign); //不是必须的,看平台的约定情况
5、$verify = openssl_verify($keyValueStr,$sign,$pkeyid,OPENSSL_ALGO_SHA256); //$sign为开放平台服务端返回的签名串
6、只有验签成功,才能确认开放平台,服务端返回数据是安全的。
<?php
namespace app\Http\Controllers\porxt;
use App\Http\Controllers\Controller;
/**
* 微信小程序支付、APP支付;支付宝APP支付
*/
class ApiController extends Controller
{
protected $keystorefile = "E:/phpstudy/WWW/gtzyc_api/certs/ton/106026.pfx";//商户证书 签名 私钥
protected $mercpublickey = "E:/phpstudy/WWW/zyc_api/certs/ton/ddkkx.pem";//对方公钥 验签
protected $keyPassWord = "";//证书密码
protected $serial_number = "";//商户证书序列号
protected $mer_id = "";//商户号
protected $mer_url="http://api.com/ext/ton/merurl";//返回用户前台展示的地址
protected $back_url="http://api.com/ext/ton/backurl";//异步通知的地址
protected $url="";//接口请求地址
// 异步通知的地址
public function backUrl(Request $req){
$param=$req->getContent();
Log::info("............异步通知:".$param);
}
// 返回用户前台展示的地址
public function merUrl(Request $req){
$param=$req->getContent();
Log::info("............返回用户前台展示的地址:".$param);
}
//请求
public function requestEtongGateWay(Request $req){
$datas=array(
"version"=>"1.1",//版本号
"format" =>"JSON",//报文格式
"charset"=>"UTF-8",//编码格式
"app_id"=>"",//应用ID
"tran_date_time"=>date("YmdHis"),//请求时间
"serial_number"=>$this->serial_number,//商户证书序列号
"content"=>array(
"mer_order_num"=>$this->mer_id.date("Ymd").$this->suiji(9),//订单号
"tran_amt"=>10,//交易金额
"sys_trace_num"=>$this->suiji(32),//请求流水号
"mer_url"=>$this->mer_url,//返回用户前台展示的地址
"back_url"=>$this->back_url,//异步通知的地址
)
);
$sign=$this->SHA256withRSA($datas);
Log::info("........统一扫码签名串:".$sign);
$datas['sign_value']=$sign;
$data=str_replace('\\', '',json_encode($datas));
$curl=$this->postcurl("QRanPay",$data);
$yanqian=$this->checkrsa($curl['data']);
}
//
public function postcurl($paytype='',$data){
$headerArray =array("Content-type:application/json;charset=UTF-8","Accept:application/json");
$url=$this->url.$paytype;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST,FALSE);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl,CURLOPT_HTTPHEADER,$headerArray);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($curl);
$err = curl_error($curl);
if ($err) {
return ['result'=>2000,'msg'=>$err];
}
if(empty($output)){
return ['result'=>2000,'msg'=>'返回数据为空'];
}
curl_close($curl);
return ['result'=>1000,'msg'=>'返回数据为空','data'=>$output];
}
/**
* 获取待签名字符串
* @param array $datas 参数数组
* @return string
*/
public function getSignString($datas){
$pairs = array();
ksort($datas['biz_content']);
reset($datas['biz_content']);
foreach ($datas['biz_content'] as $k => $v) {
$pairs[$k] = $v;
}
$pairs=json_encode($pairs);
$pairs=str_replace('\\', '', $pairs);
unset($datas['biz_content']);
$pair = array();
$datas['biz_content']=$pairs;
ksort($datas);
reset($datas);
foreach ($datas as $k => $v) {
$pair[] = "$k=$v";
}
return implode('&', $pair);
}
//验签排序
public function checkSignString($datas){
$pair = array();
ksort($datas);
reset($datas);
foreach ($datas as $k => $v) {
if(!empty($v)){
$pair[] = "$k=$v";
}
}
return implode('&', $pair);
}
/**
* 签名
* Author: Lin.
*/
public function SHA256withRSA($content)
{
$andstring=$this->getSignString($content);//排序拼接,待签名串
$filePath =$this->keystorefile;
$passphrase=$this->keyPassWord;
if(!file_exists($filePath)) {
return false;
}
$certs = array();
$pkcs12 = file_get_contents($filePath);
if (openssl_pkcs12_read($pkcs12, $certs,$passphrase)) {
$privateKey = $certs['pkey'];
$pKeyId = openssl_pkey_get_private($privateKey);
$signature='';
openssl_sign($andstring, $signature, $pKeyId,OPENSSL_ALGO_SHA256); //签名
openssl_free_key($pKeyId);
return base64_encode($signature);
} else {
Log::info(".....读取pfx错误:".openssl_error_string());
return false;
}
}
//验签
public function checkrsa($signstring){
$jsontoarr=json_decode($signstring,true);
$sign_value=$jsontoarr['sign_value'];
unset($jsontoarr['sign_value']);
$check=$this->checkSignString($jsontoarr);
// 接入方公钥
$cerfile =$this->mercpublickey;
$mercpublickey=file_get_contents($cerfile);
//摘要及签名的算法
$algo=OPENSSL_ALGO_SHA256;
//加载公钥
$publickey=openssl_pkey_get_public($mercpublickey);
//验签
$verify=openssl_verify($check,base64_decode($sign_value),$publickey,$algo);
return $verify;
}
public function suiji($num){
$a = range(0,9);
for($i=1;$i < $num+1;$i++){
$b[] = array_rand($a);
}
return join("",$b);
}
}
?>