记一次Java加密加签算法到php的坑
此文为本人原创首发于 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;
} } ?>
记一次Java加密加签算法到php的坑的更多相关文章
- java RSA 加签验签【转】
引用自: http://blog.csdn.net/wangqiuyun/article/details/42143957/ java RSA 加签验签 package com.testdemo.co ...
- ruby的加密方法整理(des rsa加密 加签)
# coding:utf-8require 'openssl'require 'base64'#des加密并且base64编码def des_encrypt des_key, des_text des ...
- 记一次Java AES 加解密 对应C# AES加解密 的一波三折
最近在跟三方对接 对方采用AES加解密 作为一个资深neter Ctrl CV 是我最大的优点 所以我义正言辞的问他们要了demo java demo代码: public class EncryptD ...
- java加密类型和算法名称
项目里有各种加密方法,但从来没有仔细研究过.一般只是copy.这几天遇到一些问题,看了一下加密代码,觉得有些疑惑. 我们知道jdk已经为我们包装好了很多的算法.但究竟包装了哪些算法,怎么去掉这些算法我 ...
- RSA加密解密及RSA加签验签
RSA安全性应用场景说明 在刚接触RSA的时候,会混淆RSA加密解密和RSA加签验签的概念.简单来说加密解密是公钥加密私钥解密,持有公钥(多人持有)可以对数据加密,但是只有持有私钥(一人持有)才可以解 ...
- RSA加密解密与加签验签
RSA公钥加密算法是1977年由罗纳德·李维斯特(Ron Rivest).阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的.1987年7月首次在美国公布 ...
- RSA加密及加签
1.对方要求我们的私钥是pkcs8格式,但是实际的公钥没有用pkcs8转换之后的私钥完成,所以是可以不是pkcs8的格式的.我们加签跟格式没有关系. 2.数据格式很重要,to_mpint而非crypt ...
- 微信支付 V3 RSA 加签踩坑
最近在做微信支付,根据微信官方文档上的要求 用RSA加签去请求支付窗口的调起,下面详细列举支付开发过程: 当前项目的流程大概是,前端根据后端要求提交数据------->拿到后台返回的prepay ...
- iOS下使用SHA1WithRSA算法加签源码
首先了解一下几个相关概念,以方便后面遇到的问题的解决: RSA算法:1977年由Ron Rivest.Adi Shamirh和LenAdleman发明的,RSA就是取自他们三个人的名字.算法基于一个数 ...
随机推荐
- 迁移MSSQL实例的所有login(包含密码)
迁移数据库的时候肯定会涉及到login的迁移(包含数据库除外). 而一般我们迁移login的时候,可能会使用在某个login上右键生成脚本这样的做法.但是这样生成的脚本不能把密码也生成出来. 而且你只 ...
- NPOI 导出Excel 数据方式
使用NPOI的库进行Excel导出操作 公共帮助类: using NPOI.HSSF.UserModel; using NPOI.SS.UserModel; using System; using S ...
- javascript中注册和移除事件的4种方式
对于html中的一些元素注册事件的方式有多种 第一种: 复制代码代码如下: <script> function test() { alert("OK"); } < ...
- Spark 集群管理命令
[启动] # 启动所有服务 start-all.sh # 启动 master start-master.sh # 启动所有 worker start-slaves.sh # 启动单个 worker s ...
- 第四次作业 重写equals方法
使用上几次用到得User实体类,在其中重写equals方法. @Override public boolean equals(Object obj) { if(obj==null)return fal ...
- KMS服务器软件-windows/OpenWRT-X64版
软件项目: https://github.com/Wind4/vlmcsd windows版kms服务器 https://files.cnblogs.com/files/SilenceRet/vlmc ...
- codeforces 671D Roads in Yusland & hdu 5293 Tree chain problem
dp dp优化 dfs序 线段树 算是一个套路.可以处理在树上取链的问题.
- python第三十课--异常(finally讲解)
演示:finally语句的作用 try: fr="" path=r'kaifanglist1.txt' fr=open(path,encoding='utf-8') print(f ...
- BZOJ3155:Preprefix sum(线段树)
Description Input 第一行给出两个整数N,M.分别表示序列长度和操作个数 接下来一行有N个数,即给定的序列a1,a2,....an 接下来M行,每行对应一个操作,格式见题目描述 Out ...
- go变量和常量
一.变量 1. 命名规则:字母或者_下划线开头 2.“:=” 这种模式只能用于函数内部,常量const不能用这种模式来定义 二.常量 1. 常量const必须在定义的时候就赋值 2. 常量的值在整个过 ...