此文为本人原创首发于 http://www.35coder.com/convert_encryption_codes_to_php/

写代码的经历中,总少不了与外部的程序对接,一旦有这样的事,往往周期会很长,很麻烦,因为你要考虑的事会多了很多,其中安全性的加密解密就是重要的一项。写代码,可以出Bug,但逼格不能弱。什么是逼格?和别人对接一下,连加密解密都没有,连验证签名都没有,别人一眼就望穿你,这就是眼界的问题了。

这次的故事是对接一个大的支付系统,对方也是第一个对接我们,然后定了接口和加解密算法,给了个Java的Demo,问了声,有没有PHP的,没有,歇菜,自己来吧。
代码说多不多,说少不少,为了先说事,代码放在最后面。

第一个坑:加密算法多多,你到底要闹咋样?

码农兄弟们可以先下去看一眼代码,然后说说它用了啥算法?
接口传输的安全性算法,首先要区分是加签名还是加密?区别是,签名+原文可以验证收到的信息是否被篡改,不过别指望有了签名就可以还原出原文来。加密就是不让别人看到原文是啥,然后给把钥匙,要让接收的人解密看出原文来。两者的算法基本上来说,就是完全不同的。

加密还分对称非对称。对称有:DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK、AES等,非对称有:RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)

还有,你以为拿个公钥就够了?当然不是,还要一对。更坑的是,可能有不同的格式。对方给了我一个keystore格式的,发觉php完全不支持,想办法转成了pem格式的。

常见的密钥格式:jks,jce,p12,pfx,bks,ubr等等
常见的证书文件格式:cer,crt,rsa,p7b,p7r,p7c,pem,p10,csr等等

最后还有一点,这次碰到的算法具体的参数。
我这次遇到的是3DES加密,`AlGORITHM = "DESede/ECB/PKCS5Padding";`,后面的类型和填充方式都不能差一点。

第二个坑:到底什么是密钥?

你会说这个很简单啊
Java里就是像这样: PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray()); 
php里就是像这样: $privateKey=openssl_pkey_get_private(file_get_contents($this->privatePemFilePath)); 
你以为你以为的就是你以为的吗?前面说了,即使算法一样,密钥格式不一样,开发语言一样,用法也完全不一样。

上面的只是格式不同,下面的还有编码的不同:
看起来我从代码里读出的密码是这个,但其实送入算法中的可不是,还要做一个Base64转换,如果你送入了错误的,会永远困在迷惘中。

     $this->dESCORPKey = C('lakala_encrypt_key');
$key = $this->$dESCORPKey;
$encryptData = self::encrypt($key, $signedReq);
...
public function encrypt($key,$data){
$decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)
$encData = openssl_encrypt($data, 'DES-EDE3', $decode_key, OPENSSL_RAW_DATA);
$encData = base64_encode($encData);
return $encData;
}

PS:网上有在线加密解密算法的工具,往往和代码出来的结果不一致,除了各种参数都需要写对以外,特别注意密码(密钥)的输入格式,要不要Base64编码或者解码。

第三个坑:带中文的字符串格式

看一眼下面的代码,你就会知道,php里有很多json_encode,json_decode,java代码里有很多getByte()这样转换成字节的操作,一个经典的问题就来了,你如果传入了中文,中文按什么格式进行编码?编码不同,决定了加密算法操作时二进制的不同,也决定了最终输出的不同。
在写代码的时候查阅了java的getByte()方法,默认值竟然是读取机器的字符格式!!!所以,写代码的时候一定要注意,最好加上具体格式,不要用默认值,要这么写:`getByte("UTF-8")`,否则换了机器表现不一样,你会死的很难看。

第四个坑:语言的问题

虽然上帝说人人平等,但现实远远复杂的多。
虽然加密解密本质上说就是一种运算变换,什么语言都可以实现的,可你不会从底层开始搞的,那就涉及语言的问题。
最简单的例子:java里具体运算时都会把String转换成byte进行操作。PHP就不行。

加密算法这个玩意虽然复杂,不过前人已经给我们造了很多轮子了,网上随便一搜就可以找到很多资料,问题是没有成系统,特别各个语言间的轮子差异还蛮大的。如何做适配,在网上的资料非常的难找。

PHP里加密方法还有mcrypt和openssl两套,互相之间的写法还差异蛮大的,让人头疼。

举个栗子(这里的Java解析使用了fastjson):

 java中:
CommReq req = JSON.parseObject(jsonStr, CommReq.class, Feature.OrderedField);
PHP中:
$req = json_decode(trim($jsonStr), true);
ksort($req);

看起来很像了吧?才不是呢!以下是输入的Json

{
"head": {
"serviceSn": "B5D79F38B96040B7B992B6BE329D9975",
"serviceId": "MPBF001",
"channelId": "05",
"inputSource": "I002",
"opId": "",
"requestTime": "20180628142105",
"versionId": "1.0.0",
"businessChannel": "LKLZFLLF"
},
"request": {
"userId":"40012345678",
"userName": "AA",
"userMobile": "18675529912",
"idNo": "110101198609096078"
}
}

问题出在具体的类型定义,以及Json解析的深度
JAVA中有定义具体按哪个类型解析

 public class CommReq implements Serializable {
private static final long serialVersionUID = 1L;
private CommReqHead head;
private String request;
}

JAVA这种强类型语言,必须定义类型,将request定义成String型,导致JSON解析出来的结果:
"request":"{\"userId\":\"40012345678\",\"userName\": \"AA\", \"userMobile\": \"18675529912\", \"idNo\": \"110101198609096078\" } ";
而PHP是弱类型语言,直接嵌套进去也解析出来了
"request": {"userId":"40012345678","userName": "AA", "userMobile": "18675529912", "idNo": "110101198609096078" } }
如果PHP要和JAVA真正保持一致(因为一旦不一致,加密结果就不一样了)

 $req = json_decode(trim($jsonStr), true);
ksort($req);
req['request']=json_encode(req['request']);

前面也提到了密钥类型的问题,其实也和语言有关,PHP只支持PEM格式,所以,还用工具对keystore进行了转换,转换之后发现几个密码都已经不需要了。生成公钥PEM和私钥PEM加上加密解密Key就可以了。

小结

回顾来看,其实只是解决了很小的一个问题,将一段JAVA代码转换成了PHP代码,甚至中间复杂的算法细节都调用原来就有的模块,更不用怀疑这些模块写的算法的正确性,但调试这样一个东西,却的的确确花费了非常大的精力。技术没有任何中间地带,只有行或者不行,容不得半分作假。开发必须要注重细节,细节到位了才不会出Bug,这点在加密解密这件事上,尤其的明显,输入差一个字符,输出完全不同。开发没有很容易的事,只有我做过,我熟悉的事。

代码在这里哦。钥匙就不提供了,这样直接copy代码跑不起来是正常的。哈哈

# JAVA

 /**
*
*/
package com.chuangmi.foundation.lakala.service.impl; import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature; import javax.annotation.PostConstruct;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.security.cert.X509Certificate;
import javax.servlet.http.HttpServletRequest; import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.chuangmi.foundation.lakala.service.SignService;
import com.chuangmi.foundation.lakala.service.models.CommReq;
import com.chuangmi.foundation.lakala.service.models.CommRequest;
import com.chuangmi.foundation.lakala.service.models.CommRes;
import com.chuangmi.foundation.lakala.service.models.CommResponse; @Service
public class SignServiceImpl implements SignService { private static final Logger logger = LoggerFactory.getLogger(SignService.class); @Value("${cer.filePath}")
private String cerFilePath; @Value("${key.filePath}")
private String keyFilePath; @Value("${key.passWord}")
private String keyPassWord; @Value("${key.alias}")
private String alias; @Value("${encrypt.key}")
private String dESCORPKey; /**
* 加密算法与填充方式
*/
public static final String AlGORITHM = "DESede/ECB/PKCS5Padding"; // 定义加密算法,可用 @PostConstruct
public void init(){
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){ System.out.println("security provider BC not found, will add provider"); Security.addProvider(new BouncyCastleProvider()); }
} /**
* 加签并加密需要发送的请求报文
* @param 接口报文
* @return 加签后可以发送的json密文
* @throws JSONException
*/
@Override
public String signRequestJsonToSend(String jsonStr) throws JSONException {
JSONObject resultObj = null;
if(jsonStr.indexOf("\"head\"") >= 0)
{
CommReq req = JSON.parseObject(jsonStr, CommReq.class, Feature.OrderedField);
// 对报文体签名
String signData = signData(req.getRequest()); logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData); req.getHead().setSignData(signData);
resultObj = JSONObject.parseObject(JSON.toJSONString(req), Feature.OrderedField);
}
else if(jsonStr.indexOf("\"comm\"") >= 0)
{
CommRequest req = JSON.parseObject(jsonStr, CommRequest.class, Feature.OrderedField);
// 对报文体签名
String signData = signData(req.getData()); logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData); req.getComm().setSigntx(signData);
resultObj = JSONObject.parseObject(JSON.toJSONString(req), Feature.OrderedField);
} String signedReq = String.valueOf(JSONObject.toJSON(resultObj));
logger.info("加签后的报文:" + signedReq); //加密
byte[] key = Base64.decodeBase64(dESCORPKey);
byte[] encryptData = encrypt(key, signedReq.getBytes()); String encryptStr = Base64.encodeBase64String(encryptData); logger.info("加密成功,密文:" + encryptStr); return encryptStr;
} /**
* 加签并加密需要发送的响应报文
* @param 接口报文
* @return 加签后可以发送的json密文
* @throws JSONException
*/
@Override
public String signResponseJsonToSend(String jsonStr) throws JSONException {
JSONObject resultObj = null;
if(jsonStr.indexOf("\"head\"") >= 0)
{
CommRes response = JSON.parseObject(jsonStr, CommRes.class, Feature.OrderedField);
resultObj = JSONObject.parseObject(JSON.toJSONString(response), Feature.OrderedField);
}
else if(jsonStr.indexOf("\"comm\"") >= 0)
{
CommResponse response = JSON.parseObject(jsonStr, CommResponse.class, Feature.OrderedField);
// 对报文体签名
String signData = signData(response.getData()); logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData); response.getComm().setSigntx(signData);
resultObj = JSONObject.parseObject(JSON.toJSONString(response), Feature.OrderedField);
} String signedReq = String.valueOf(JSONObject.toJSON(resultObj));
logger.info("加签后的响应报文:" + signedReq); //加密
byte[] key = Base64.decodeBase64(dESCORPKey);
byte[] encryptData = encrypt(key, signedReq.getBytes()); String encryptStr = Base64.encodeBase64String(encryptData); logger.info("加密成功的响应报文,密文:" + encryptStr); return encryptStr;
} /**
* 从request提取json data,并解密,验签, 验签成功则返回data,否则返回null
* @param request
* @return
* @throws IOException
* @throws JSONException
*/
@Override
public String verifyReceivedRequest(HttpServletRequest request) throws IOException, JSONException {
String json = extractJson(request);
logger.info("接收报文密文:" + json); return verifyRequestJson(json);
} /**
* 对收到的请求json解密,验签, 验签成功则返回data,否则返回null
* @param json
* @return
* @throws UnsupportedEncodingException
* @throws JSONException
*/
@Override
public String verifyRequestJson(String json) throws UnsupportedEncodingException, JSONException {
//解密
byte[] key = Base64.decodeBase64(dESCORPKey);
byte[] decryptBytes = decrypt(key, Base64.decodeBase64(json));
String orig = new String(decryptBytes, "UTF-8");
logger.info("【收到的报文请求】接收报文:" + orig); // 验签
JSONObject obj = JSONObject.parseObject(orig, Feature.OrderedField);
String reqStr = String.valueOf(JSONObject.toJSON(obj));
if(reqStr.indexOf("\"comm\"") >= 0)
{
CommRequest req = JSON.parseObject(reqStr, CommRequest.class, Feature.OrderedField);
String signtx = req.getComm().getSigntx(); logger.info("报文中的签名:" + signtx);
boolean nRet = verifyData(req.getData(), signtx);
if(nRet)
{
req.getComm().setSigntx("");
return JSON.toJSONString(req);
}
else
{
return null;
}
}
else if(reqStr.indexOf("\"head\"") >= 0)
{
CommReq req = JSON.parseObject(reqStr, CommReq.class, Feature.OrderedField);
String signData = req.getHead().getSignData(); logger.info("报文中的签名:" + signData);
boolean nRet = verifyData(req.getRequest(), signData);
if(nRet)
{
req.getHead().setSignData("");
return JSON.toJSONString(req);
}
else
{
return null;
}
}
else
{
return null;
}
} /**
* 对响应的报文json解密,验签, 验签成功则返回data,否则返回null
* @param json
* @return
* @throws UnsupportedEncodingException
* @throws JSONException
*/
@Override
public String verifyResponseJson(String json) throws UnsupportedEncodingException, JSONException {
//解密
byte[] key = Base64.decodeBase64(dESCORPKey);
byte[] decryptBytes = decrypt(key, Base64.decodeBase64(json));
String orig = new String(decryptBytes, "UTF-8");
logger.info("【收到的响应报文】报文:" + orig); // 验签
JSONObject obj = JSONObject.parseObject(orig, Feature.OrderedField);
String reqStr = String.valueOf(JSONObject.toJSON(obj));
if(reqStr.indexOf("\"comm\"") >= 0)
{
CommResponse response = JSON.parseObject(reqStr, CommResponse.class, Feature.OrderedField);
String signtx = response.getComm().getSigntx(); logger.info("报文中的签名:" + signtx);
boolean nRet = verifyData(response.getData(), signtx);
if(nRet)
{
response.getComm().setSigntx("");
return JSON.toJSONString(response);
}
else
{
return null;
}
}
else if(reqStr.indexOf("\"head\"") >= 0)
{
CommRes response = JSON.parseObject(reqStr, CommRes.class, Feature.OrderedField);
return JSON.toJSONString(response);
}
else
{
return null;
}
} public String extractJson(HttpServletRequest request) throws IOException {
//用于接收对方的jsonString
StringBuilder jsonString = new StringBuilder();
BufferedReader reader = request.getReader();
try {
String line;
while ((line = reader.readLine()) != null) {
jsonString.append(line);
}
} finally {
reader.close();
}
String data = jsonString.toString();
return data;
} /* 使用私钥签名,并返回密文
* @param param 需要进行签名的数据
* @return 签名
*/
private String signData(String param)
{
InputStream inputStream = null;
try { String store_password = keyPassWord;// 密钥库密码
String password = keyPassWord;// 私钥密码
String keyAlias = alias;// 别名
// a. 创建针对jks文件的输入流 inputStream = new FileInputStream(keyFilePath);// CA 文件名 如: D://tmp/encrypt.jks
// input = getClass().getClassLoader().getResourceAsStream(keyFile);
// 如果制定classpath下面的证书文件 // b. 创建KeyStore实例 (store_password密钥库密码)
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(inputStream, store_password.toCharArray()); // c. 获取私钥 (keyAlias 为私钥别名,password为私钥密码)
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray()); // 实例化一个用SHA算法进行散列,用RSA算法进行加密的Signature.
Signature dsa = Signature.getInstance("SHA1withRSA");
// 加载加密散列码用的私钥
dsa.initSign(privateKey);
// 进行散列,对产生的散列码进行加密并返回
byte[] p = param.getBytes();
dsa.update(p); return Base64.encodeBase64String(dsa.sign());// 进行签名, 加密后的也是二进制的,但是返回给调用方是字符串,将byte[]转为base64编码 } catch (Exception gse) { gse.printStackTrace();
return null; } finally { try {
if (inputStream != null)
inputStream.close();// 判断
} catch (Exception e) {
e.printStackTrace();
}
} } /* 用公钥证书对字符串进行签名验证
* @param urlParam 需要进行签名验证的数据
* @param signParam 编码后的签名
* @return 校验签名,true为正确 false为错误
*/
private boolean verifyData(String urlParam, String signParam)
{
boolean verifies = false; InputStream in = null; try { // a. 创建针对cer文件的输入流
InputStream inputStream = new FileInputStream(cerFilePath);// CA 文件名 如: D://tmp/cerfile.p7b
// input = getClass().getClassLoader().getResourceAsStream(keyFile);
// 如果制定classpath下面的证书文件 // b. 创建KeyStore实例 (store_password密钥库密码)
X509Certificate cert = X509Certificate.getInstance(inputStream); // c. 获取公钥 (keyAlias 为公钥别名)
PublicKey pubKey = cert.getPublicKey(); if (pubKey != null) {
// d. 公钥进行验签
// 获取Signature实例,指定签名算法(与之前一致)
Signature dsa = Signature.getInstance("SHA1withRSA");
// 加载公钥
dsa.initVerify(pubKey);
// 更新原数据
dsa.update(urlParam.getBytes()); // 公钥验签(true-验签通过;false-验签失败)
verifies = dsa.verify(Base64.decodeBase64(signParam));// 将签名数据从base64编码字符串转回字节数组
} } catch (Exception gse) {
gse.printStackTrace();
} finally { try {
if (in != null)
in.close();// 判断
} catch (Exception e) {
e.printStackTrace();
}
}
return verifies; } // DES,DESede,Blowfish
/**
* 使用3des加密明文
*
* @param byte[] key: 密钥
* @param byte[] src: 明文
*
*/
private byte[] encrypt(byte[] key, byte[] src) {
try { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){ System.out.println("security provider BC not found, will add provider"); Security.addProvider(new BouncyCastleProvider()); } // 生成密钥
SecretKey deskey = new SecretKeySpec(key, AlGORITHM);
// 加密
Cipher c1 = Cipher.getInstance(AlGORITHM);
c1.init(Cipher.ENCRYPT_MODE, deskey);
return c1.doFinal(src);// 在单一方面的加密或解密
} catch (java.security.NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (javax.crypto.NoSuchPaddingException e2) {
e2.printStackTrace();
} catch (java.lang.Exception e3) {
e3.printStackTrace();
}
return null;
} /**
* 使用3des解密密文
*
* @param byte[] key: 密钥
* @param byte[] src: 密文
*
*/
private byte[] decrypt(byte[] keybyte, byte[] src) {
try { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){ System.out.println("security provider BC not found, will add provider"); Security.addProvider(new BouncyCastleProvider()); } // 生成密钥
SecretKey deskey = new SecretKeySpec(keybyte, AlGORITHM);
// 解密
Cipher c1 = Cipher.getInstance(AlGORITHM);
c1.init(Cipher.DECRYPT_MODE, deskey);
return c1.doFinal(src);
} catch (java.security.NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (javax.crypto.NoSuchPaddingException e2) {
e2.printStackTrace();
} catch (java.lang.Exception e3) {
e3.printStackTrace();
} return null;
} public static void main(String[] args) throws JSONException { InputStream inputStream = null;
try { String store_password = "123456";// 密钥库密码
String password = "123456";// 私钥密码
String keyAlias = "www.lakala.com";// 别名
// a. 创建针对jks文件的输入流 inputStream = new FileInputStream("/Users/rinson/eclipse-workspace/lakala/lakala-server/asdc.keystore");// CA 文件名 如: D://tmp/encrypt.jks
// input = getClass().getClassLoader().getResourceAsStream(keyFile);
// 如果制定classpath下面的证书文件 // b. 创建KeyStore实例 (store_password密钥库密码)
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(inputStream, store_password.toCharArray()); // c. 获取私钥 (keyAlias 为私钥别名,password为私钥密码)
PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray()); // 实例化一个用SHA算法进行散列,用RSA算法进行加密的Signature.
Signature dsa = Signature.getInstance("SHA1withRSA");
// 加载加密散列码用的私钥
dsa.initSign(privateKey);
String param = "XXXXX";
// 进行散列,对产生的散列码进行加密并返回
dsa.update(param .getBytes()); System.out.println(Base64.encodeBase64String(dsa.sign()));// 进行签名, 加密后的也是二进制的,但是返回给调用方是字符串,将byte[]转为base64编码 } catch (Exception gse) { gse.printStackTrace(); } finally { try {
if (inputStream != null)
inputStream.close();// 判断
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

# PHP

 <?php

 namespace Common\Lib\Lakala;

 class SignService
{
private $publicPemFilePath='';
private $privatePemFilePath='';
private $dESCORPKey = ''; //初始化
public function __construct()
{
$this->publicPemFilePath = C('lakala_cer_filePath');
$this->privatePemFilePath=C('lakala_key_filePath');
$this->dESCORPKey = C('lakala_encrypt_key');
} /**
* 加签并加密需要发送的请求报文
* @param $head 是java类CommReqHead,需在调用时传入
* @param $data 是具体的请求参数数组
*
*/
public function ToLakala($head,$data,$url){
//CommReq
$ret = [
'head'=>$head,
'request'=>$data,
];
$ret = json_encode($ret);
//会对整个请求body加签加密,返回字符串body体
$params = $this->signRequestJsonToSend($ret); //http request
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json; charset=utf-8', 'Content-Length: ' . strlen($data)));
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
$result = curl_exec($ch);
curl_close($ch);
$result = $params;
//验证返回结果
$response = $this->verifyResponseJson($result);
//结果返回
return $response;
} public function FromLakala(){
$lakalaSign = new SignService();
$params = I('');
$params = $this->verifyRequestJson($params);
return $params;
} public function FromLakalaResponse($head,$response){
$ret = [
'head'=>$head,
'response'=>$response,
];
$res = $this->signResponseJsonToSend($ret);
return $res;
} /**
* 加签并加密需要发送的请求报文
* @param $jsonStr 接口报文(String类型)
* @return 加签后可以发送的json密文
*/
public function signRequestJsonToSend($jsonStr)
{
if(strpos($jsonStr,"\"head\"")!= false)
{
$req = json_decode(trim($jsonStr), true);
ksort($req);
// 对报文体签名
$signData = $this->signData($req['request']);
$req['head']['signData']= $signData;
$req['request']=json_encode($req['request'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
ksort($req['head']);
$resultObj = $req; }
else if(strpos($jsonStr,"\"comm\"")!= false)
{
$req = json_decode(trim($jsonStr), true);
ksort($req);
// 对报文体签名
$signData = $this->signData($req['data']);
$req['comm']['signtx']=$signData;
$req['data']=json_encode($req['data'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
ksort($req['head']);
$resultObj = $req;
} $signedReq = json_encode($resultObj,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES); //logger.info("加签后的报文:" + signedReq);
//此处直接放入要加密的数据
$key = $this->dESCORPKey;
$encryptData = self::encrypt($key,$signedReq);
//logger.info("加密成功,密文:" + encryptStr); return $encryptData;
} /**
* 加签并加密需要发送的响应报文
* @param $jsonStr 接口报文(String类型)
* @return 加签后可以发送的json密文
*/
public function signResponseJsonToSend($jsonStr) {
if(strpos($jsonStr,"\"head\"")!= false)
{
$response = json_decode(trim($jsonStr), true);
$resultObj = json_decode(json_encode($response));
}
else if(strpos($jsonStr,"\"comm\"")!= false)
{
$response = json_decode(trim($jsonStr), true);
ksort($response);
// 对报文体签名
$signData = $this->signData($response['data']); //logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData);
$response['comm']['signTx']=$signData;
$response['data']=json_encode($response['data'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
ksort($response['comm']);
$resultObj = $response;
} $signedReq = json_encode($resultObj,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
//logger.info("加签后的响应报文:" + signedReq); $key = $this->$dESCORPKey;
$encryptData = self::encrypt($key, $signedReq); //logger.info("加密成功的响应报文,密文:" + encryptStr);
return $encryptData;
} /**
* 对响应的报文json解密,验签, 验签成功则返回data,否则返回null
* @param json (String)
* @return (String)
*/
public function verifyResponseJson($json) {
//解密
$key = $this->dESCORPKey;
$decryptBytes = self::decrypt($key, $json); //logger.info("【收到的响应报文】报文:" + orig); // 验签
$obj = json_decode($decryptBytes);
$reqStr = json_encode($obj);
if(strpos($reqStr,"\"comm\"")!= false)
{
$response = json_decode($reqStr,true);
$signtx = $response['comm']['signtx']; //logger.info("报文中的签名:" + signtx);
$nRet = $this->verifyData($response['data'], $signtx);
if($nRet)
{
$response['comm']['signtx']="";
return json_encode($response);
}
else
{
return null;
}
}
else if(strpos($reqStr,"\"head\"")!= false)
{
return $reqStr;
}
else
{
return null;
}
} /**
* 对收到的请求json解密,验签, 验签成功则返回data,否则返回null
* @param json (String)
* @return (String)
*/
public function verifyRequestJson($json) {
//解密
$key = $this->dESCORPKey;
$decryptBytes = self::decrypt($key, $json); // 验签
$obj = json_decode($decryptBytes);
$reqStr = json_encode($obj);
if(strpos($reqStr,"\"comm\"")!= false)
{
$req = json_decode($reqStr,true);
ksort($req);
$signtx = $req['comm']['signtx']; //logger.info("报文中的签名:" + signtx);
$nRet = $this->verifyData($req['data'], $signtx);
if($nRet)
{
$req['comm']['signtx']="";
return json_encode($req);
}
else
{
return null;
}
}
else if(strpos($reqStr,"\"head\"")!= false)
{
$req = json_decode($reqStr,true);
ksort($req);
$signData = $req['head']['signData'];
//logger.info("报文中的签名:" + signData);
$nRet = $this->verifyData($req['request'], $signData);
return $nRet;
if($nRet)
{
$req['head']['signData']="";
return json_encode($req);
}
else
{
return null;
}
}
else
{
return null;
}
}
/* 使用私钥签名,并返回密文
* @param param 需要进行签名的数据(Array)
* @return 签名(加签字符串String)
*/
private function signData($param)
{
$content = json_encode($param,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
$privateKey=openssl_pkey_get_private(file_get_contents($this->privatePemFilePath));
openssl_sign($content, $signature, $privateKey);
openssl_free_key($privateKey);
return base64_encode($signature); } /* 用公钥证书对字符串进行签名验证
* @param urlParam 需要进行签名验证的数据(直接从解密报文中取出的String)
* @param signParam 编码后的签名(直接从解密报文中取出的String)
* @return 校验签名,true为正确 false为错误
*/
private function verifyData($urlParam,$signParam)
{
$signature = base64_decode($signParam);
$pubkeyid = openssl_pkey_get_public(file_get_contents($publicPemFilePath));
// state whether signature is okay or not
$verifies = openssl_verify($urlParam, $signature, $pubkeyid);
return $verifies;
} /**
* @param $key获取的密钥字符串(直接从配置文件中取出)
* @param $data (字符串)
* @return string
*/
public function encrypt($key,$data){
$decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)
$encData = openssl_encrypt($data, 'DES-EDE3', $decode_key, OPENSSL_RAW_DATA);
$encData = base64_encode($encData);
return $encData;
} /**
* @param $key获取的密钥字符串(直接从配置文件中取出)
* @param $data (字符串)
* @return string
*/
public function decrypt($key,$data){
$decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)
$data = base64_decode($data);//此处需要BASE64解码(变为2进制)
$decData = openssl_decrypt($data, 'DES-EDE3', $decode_key, OPENSSL_RAW_DATA);
return $decData;
} } ?>

最新文章

  1. 小白解决CENTOS7错误:Cannot find a valid baseurl for repo: base/7/x86_6
  2. ASP.NET Core 中文文档 第三章 原理(15)请求功能
  3. SqlServer 笔记二 获取汉字的拼音首字母
  4. wordpres 自定义comment样式
  5. qt qml 九宫格划指锁屏视图
  6. python——django的post请求
  7. MongoDB的C#官方驱动InvalidOperationException异常的解决办法
  8. 内存管理范围和@property
  9. Mybatis中配置Mapper的方法
  10. NOSQL之【WIN7的安装配置】
  11. 如何通过ftell和fseek来获取文件大小
  12. cf455A Boredom
  13. STL源码剖析 迭代器(iterator)概念与编程技法(三)
  14. vue.js移动端app实战1:初始配置
  15. 5linux引导流程解析
  16. Excel条件格式
  17. struts2框架学习笔记3:获取servletAPI
  18. Android动画总结
  19. uboot启动过程理解
  20. aws代理

热门文章

  1. python常用模块之subprocess
  2. 为何SQL SERVER使用sa账号登录还原数据库BAK文件失败,但是使用windows登录就可以
  3. 一张思维导图纵观MySQL数据安全体系!
  4. Rarfile解压不了的问题
  5. 【Weex学习】环境搭建
  6. Asp.net中DataTable的排序功能
  7. 禁止选择DIV内的文本(css,js写法)
  8. Winfrom 使用WCF 实现双工通讯
  9. 【转】Poco 1.4.2 HTTPClientSession/HTTPRequest 使用使用代理(proxy)需要注意的一点
  10. 6、JUC--同步锁Lock