ksort java_记一次Java加密加签算法到php的坑

本文讲述了在对接大型支付系统时,将Java加密加签算法转换为PHP的过程中遇到的四大坑,包括加密算法选择、密钥格式、中文字符串处理和语言差异。详细介绍了每个坑的解决办法,如Java的DESede加密算法在PHP中的实现,以及不同语言间加密解密库的差异。最后强调了开发中注重细节的重要性。
摘要由CSDN通过智能技术生成

写代码的经历中,总少不了与外部的程序对接,一旦有这样的事,往往周期会很长,很麻烦,因为你要考虑的事会多了很多,其中安全性的加密解密就是重要的一项。写代码,可以出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转换,如果你送入了错误的,会永远困在迷惘中。

1 $this->dESCORPKey = C('lakala_encrypt_key');2 $key = $this->$dESCORPKey;3 $encryptData = self::encrypt($key, $signedReq);4 ...

5 public function encrypt($key,$data){6 $decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)

7 $encData = openssl_encrypt($data, 'DES-EDE3', $decode_key,OPENSSL_RAW_DATA);8 $encData = base64_encode($encData);9 return $encData;10 }

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

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

看一眼下面的代码,你就会知道,php里有很多json_encode,json_decode,java代码里有很多getByte()这样转换成字节的操作,一个经典的问题就来了,你如果传入了中文,中文按什么格式进行编码?编码不同,决定了加密算法操作时二进制的不同,也决定了最终输出的不同。

在写代码的时候查阅了java的getByte()方法,默认值竟然是读取机器的字符格式!!!所以,写代码的时候一定要注意,最好加上具体格式,不要用默认值,要这么写:`getByte("UTF-8")`,否则换了机器表现不一样,你会死的很难看。

第四个坑:语言的问题

虽然上帝说人人平等,但现实远远复杂的多。

虽然加密解密本质上说就是一种运算变换,什么语言都可以实现的,可你不会从底层开始搞的,那就涉及语言的问题。

最简单的例子:java里具体运算时都会把String转换成byte进行操作。PHP就不行。

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

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

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

1 java中:2 CommReq req = JSON.parseObject(jsonStr, CommReq.class, Feature.OrderedField);3 PHP中:4 $req = json_decode(trim($jsonStr), true);5 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中有定义具体按哪个类型解析

1 public class CommReq implementsSerializable {2 private static final long serialVersionUID = 1L;3 privateCommReqHead head;4 privateString request;5 }

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真正保持一致(因为一旦不一致,加密结果就不一样了)

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

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

小结

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

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

# JAVA

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 /**

2 *3 */

4 packagecom.chuangmi.foundation.lakala.service.impl;5

6 importjava.io.BufferedReader;7 importjava.io.FileInputStream;8 importjava.io.IOException;9 importjava.io.InputStream;10 importjava.io.UnsupportedEncodingException;11 importjava.security.KeyStore;12 importjava.security.PrivateKey;13 importjava.security.PublicKey;14 importjava.security.Security;15 importjava.security.Signature;16

17 importjavax.annotation.PostConstruct;18 importjavax.crypto.Cipher;19 importjavax.crypto.SecretKey;20 importjavax.crypto.spec.SecretKeySpec;21 importjavax.security.cert.X509Certificate;22 importjavax.servlet.http.HttpServletRequest;23

24 importorg.apache.commons.codec.binary.Base64;25 importorg.bouncycastle.jce.provider.BouncyCastleProvider;26 importorg.slf4j.Logger;27 importorg.slf4j.LoggerFactory;28 importorg.springframework.beans.factory.annotation.Value;29 importorg.springframework.stereotype.Service;30

31 importcom.alibaba.fastjson.JSON;32 importcom.alibaba.fastjson.JSONException;33 importcom.alibaba.fastjson.JSONObject;34 importcom.alibaba.fastjson.parser.Feature;35 importcom.chuangmi.foundation.lakala.service.SignService;36 importcom.chuangmi.foundation.lakala.service.models.CommReq;37 importcom.chuangmi.foundation.lakala.service.models.CommRequest;38 importcom.chuangmi.foundation.lakala.service.models.CommRes;39 importcom.chuangmi.foundation.lakala.service.models.CommResponse;40

41

42 @Service43 public class SignServiceImpl implementsSignService {44

45 private static final Logger logger = LoggerFactory.getLogger(SignService.class);46

47 @Value("${cer.filePath}")48 privateString cerFilePath;49

50 @Value("${key.filePath}")51 privateString keyFilePath;52

53 @Value("${key.passWord}")54 privateString keyPassWord;55

56 @Value("${key.alias}")57 privateString alias;58

59 @Value("${encrypt.key}")60 privateString dESCORPKey;61

62 /**

63 * 加密算法与填充方式64 */

65 public static final String AlGORITHM = "DESede/ECB/PKCS5Padding"; //定义加密算法,可用

66

67 @PostConstruct68 public voidinit(){69 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){70

71 System.out.println("security provider BC not found, will add provider");72

73 Security.addProvider(newBouncyCastleProvider());74

75 }76 }77

78 /**

79 * 加签并加密需要发送的请求报文80 *@param接口报文81 *@return加签后可以发送的json密文82 *@throwsJSONException83 */

84 @Override85 public String signRequestJsonToSend(String jsonStr) throwsJSONException {86 JSONObject resultObj = null;87 if(jsonStr.indexOf("\"head\"") >= 0)88 {89 CommReq req = JSON.parseObject(jsonStr, CommReq.class, Feature.OrderedField);90 //对报文体签名

91 String signData =signData(req.getRequest());92

93 logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" +signData);94

95 req.getHead().setSignData(signData);96 resultObj =JSONObject.parseObject(JSON.toJSONString(req), Feature.OrderedField);97 }98 else if(jsonStr.indexOf("\"comm\"") >= 0)99 {100 CommRequest req = JSON.parseObject(jsonStr, CommRequest.class, Feature.OrderedField);101 //对报文体签名

102 String signData =signData(req.getData());103

104 logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" +signData);105

106 req.getComm().setSigntx(signData);107 resultObj =JSONObject.parseObject(JSON.toJSONString(req), Feature.OrderedField);108 }109

110 String signedReq =String.valueOf(JSONObject.toJSON(resultObj));111 logger.info("加签后的报文:" +signedReq);112

113 //加密

114 byte[] key =Base64.decodeBase64(dESCORPKey);115 byte[] encryptData =encrypt(key, signedReq.getBytes());116

117 String encryptStr =Base64.encodeBase64String(encryptData);118

119 logger.info("加密成功,密文:" +encryptStr);120

121 returnencryptStr;122 }123

124 /**

125 * 加签并加密需要发送的响应报文126 *@param接口报文127 *@return加签后可以发送的json密文128 *@throwsJSONException129 */

130 @Override131 public String signResponseJsonToSend(String jsonStr) throwsJSONException {132 JSONObject resultObj = null;133 if(jsonStr.indexOf("\"head\"") >= 0)134 {135 CommRes response = JSON.parseObject(jsonStr, CommRes.class, Feature.OrderedField);136 resultObj =JSONObject.parseObject(JSON.toJSONString(response), Feature.OrderedField);137 }138 else if(jsonStr.indexOf("\"comm\"") >= 0)139 {140 CommResponse response = JSON.parseObject(jsonStr, CommResponse.class, Feature.OrderedField);141 //对报文体签名

142 String signData =signData(response.getData());143

144 logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" +signData);145

146 response.getComm().setSigntx(signData);147 resultObj =JSONObject.parseObject(JSON.toJSONString(response), Feature.OrderedField);148 }149

150 String signedReq =String.valueOf(JSONObject.toJSON(resultObj));151 logger.info("加签后的响应报文:" +signedReq);152

153 //加密

154 byte[] key =Base64.decodeBase64(dESCORPKey);155 byte[] encryptData =encrypt(key, signedReq.getBytes());156

157 String encryptStr =Base64.encodeBase64String(encryptData);158

159 logger.info("加密成功的响应报文,密文:" +encryptStr);160

161 returnencryptStr;162 }163

164 /**

165 * 从request提取json data,并解密,验签, 验签成功则返回data,否则返回null166 *@paramrequest167 *@return

168 *@throwsIOException169 *@throwsJSONException170 */

171 @Override172 public String verifyReceivedRequest(HttpServletRequest request) throwsIOException, JSONException {173 String json =extractJson(request);174 logger.info("接收报文密文:" +json);175

176 returnverifyRequestJson(json);177 }178

179 /**

180 * 对收到的请求json解密,验签, 验签成功则返回data,否则返回null181 *@paramjson182 *@return

183 *@throwsUnsupportedEncodingException184 *@throwsJSONException185 */

186 @Override187 public String verifyRequestJson(String json) throwsUnsupportedEncodingException, JSONException {188 //解密

189 byte[] key =Base64.decodeBase64(dESCORPKey);190 byte[] decryptBytes =decrypt(key, Base64.decodeBase64(json));191 String orig = new String(decryptBytes, "UTF-8");192 logger.info("【收到的报文请求】接收报文:" +orig);193

194 //验签

195 JSONObject obj =JSONObject.parseObject(orig, Feature.OrderedField);196 String reqStr =String.valueOf(JSONObject.toJSON(obj));197 if(reqStr.indexOf("\"comm\"") >= 0)198 {199 CommRequest req = JSON.parseObject(reqStr, CommRequest.class, Feature.OrderedField);200 String signtx =req.getComm().getSigntx();201

202 logger.info("报文中的签名:" +signtx);203 boolean nRet =verifyData(req.getData(), signtx);204 if(nRet)205 {206 req.getComm().setSigntx("");207 returnJSON.toJSONString(req);208 }209 else

210 {211 return null;212 }213 }214 else if(reqStr.indexOf("\"head\"") >= 0)215 {216 CommReq req = JSON.parseObject(reqStr, CommReq.class, Feature.OrderedField);217 String signData =req.getHead().getSignData();218

219 logger.info("报文中的签名:" +signData);220 boolean nRet =verifyData(req.getRequest(), signData);221 if(nRet)222 {223 req.getHead().setSignData("");224 returnJSON.toJSONString(req);225 }226 else

227 {228 return null;229 }230 }231 else

232 {233 return null;234 }235 }236

237 /**

238 * 对响应的报文json解密,验签, 验签成功则返回data,否则返回null239 *@paramjson240 *@return

241 *@throwsUnsupportedEncodingException242 *@throwsJSONException243 */

244 @Override245 public String verifyResponseJson(String json) throwsUnsupportedEncodingException, JSONException {246 //解密

247 byte[] key =Base64.decodeBase64(dESCORPKey);248 byte[] decryptBytes =decrypt(key, Base64.decodeBase64(json));249 String orig = new String(decryptBytes, "UTF-8");250 logger.info("【收到的响应报文】报文:" +orig);251

252 //验签

253 JSONObject obj =JSONObject.parseObject(orig, Feature.OrderedField);254 String reqStr =String.valueOf(JSONObject.toJSON(obj));255 if(reqStr.indexOf("\"comm\"") >= 0)256 {257 CommResponse response = JSON.parseObject(reqStr, CommResponse.class, Feature.OrderedField);258 String signtx =response.getComm().getSigntx();259

260 logger.info("报文中的签名:" +signtx);261 boolean nRet =verifyData(response.getData(), signtx);262 if(nRet)263 {264 response.getComm().setSigntx("");265 returnJSON.toJSONString(response);266 }267 else

268 {269 return null;270 }271 }272 else if(reqStr.indexOf("\"head\"") >= 0)273 {274 CommRes response = JSON.parseObject(reqStr, CommRes.class, Feature.OrderedField);275 returnJSON.toJSONString(response);276 }277 else

278 {279 return null;280 }281 }282

283 public String extractJson(HttpServletRequest request) throwsIOException {284 //用于接收对方的jsonString

285 StringBuilder jsonString = newStringBuilder();286 BufferedReader reader =request.getReader();287 try{288 String line;289 while ((line = reader.readLine()) != null) {290 jsonString.append(line);291 }292 } finally{293 reader.close();294 }295 String data =jsonString.toString();296 returndata;297 }298

299 /*使用私钥签名,并返回密文300 * @param param 需要进行签名的数据301 * @return 签名302 */

303 privateString signData(String param)304 {305 InputStream inputStream = null;306 try{307

308 String store_password = keyPassWord;//密钥库密码

309 String password = keyPassWord;//私钥密码

310 String keyAlias = alias;//别名311 //a. 创建针对jks文件的输入流

312

313 inputStream = new FileInputStream(keyFilePath);//CA 文件名 如: D://tmp/encrypt.jks314 //input = getClass().getClassLoader().getResourceAsStream(keyFile);315 //如果制定classpath下面的证书文件316

317 //b. 创建KeyStore实例 (store_password密钥库密码)

318 KeyStore keyStore = KeyStore.getInstance("JKS");319 keyStore.load(inputStream, store_password.toCharArray());320

321 //c. 获取私钥 (keyAlias 为私钥别名,password为私钥密码)

322 PrivateKey privateKey =(PrivateKey) keyStore.getKey(keyAlias, password.toCharArray());323

324 //实例化一个用SHA算法进行散列,用RSA算法进行加密的Signature.

325 Signature dsa = Signature.getInstance("SHA1withRSA");326 //加载加密散列码用的私钥

327 dsa.initSign(privateKey);328 //进行散列,对产生的散列码进行加密并返回

329 byte[] p =param.getBytes();330 dsa.update(p);331

332 return Base64.encodeBase64String(dsa.sign());//进行签名, 加密后的也是二进制的,但是返回给调用方是字符串,将byte[]转为base64编码

333

334 } catch(Exception gse) {335

336 gse.printStackTrace();337 return null;338

339 } finally{340

341 try{342 if (inputStream != null)343 inputStream.close();//判断

344 } catch(Exception e) {345 e.printStackTrace();346 }347 }348

349 }350

351 /*用公钥证书对字符串进行签名验证352 * @param urlParam 需要进行签名验证的数据353 * @param signParam 编码后的签名354 * @return 校验签名,true为正确 false为错误355 */

356 private booleanverifyData(String urlParam, String signParam)357 {358 boolean verifies = false;359

360 InputStream in = null;361

362 try{363

364 //a. 创建针对cer文件的输入流

365 InputStream inputStream = new FileInputStream(cerFilePath);//CA 文件名 如: D://tmp/cerfile.p7b366 //input = getClass().getClassLoader().getResourceAsStream(keyFile);367 //如果制定classpath下面的证书文件368

369 //b. 创建KeyStore实例 (store_password密钥库密码)

370 X509Certificate cert =X509Certificate.getInstance(inputStream);371

372 //c. 获取公钥 (keyAlias 为公钥别名)

373 PublicKey pubKey =cert.getPublicKey();374

375 if (pubKey != null) {376 //d. 公钥进行验签377 //获取Signature实例,指定签名算法(与之前一致)

378 Signature dsa = Signature.getInstance("SHA1withRSA");379 //加载公钥

380 dsa.initVerify(pubKey);381 //更新原数据

382 dsa.update(urlParam.getBytes());383

384 //公钥验签(true-验签通过;false-验签失败)

385 verifies = dsa.verify(Base64.decodeBase64(signParam));//将签名数据从base64编码字符串转回字节数组

386 }387

388 } catch(Exception gse) {389 gse.printStackTrace();390 } finally{391

392 try{393 if (in != null)394 in.close();//判断

395 } catch(Exception e) {396 e.printStackTrace();397 }398 }399 returnverifies;400

401 }402

403

404 //DES,DESede,Blowfish

405 /**

406 * 使用3des加密明文407 *408 *@parambyte[] key: 密钥409 *@parambyte[] src: 明文410 *411 */

412 private byte[] encrypt(byte[] key, byte[] src) {413 try{414

415 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){416

417 System.out.println("security provider BC not found, will add provider");418

419 Security.addProvider(newBouncyCastleProvider());420

421 }422

423 //生成密钥

424 SecretKey deskey = newSecretKeySpec(key, AlGORITHM);425 //加密

426 Cipher c1 =Cipher.getInstance(AlGORITHM);427 c1.init(Cipher.ENCRYPT_MODE, deskey);428 return c1.doFinal(src);//在单一方面的加密或解密

429 } catch(java.security.NoSuchAlgorithmException e1) {430 e1.printStackTrace();431 } catch(javax.crypto.NoSuchPaddingException e2) {432 e2.printStackTrace();433 } catch(java.lang.Exception e3) {434 e3.printStackTrace();435 }436 return null;437 }438

439 /**

440 * 使用3des解密密文441 *442 *@parambyte[] key: 密钥443 *@parambyte[] src: 密文444 *445 */

446 private byte[] decrypt(byte[] keybyte, byte[] src) {447 try{448

449 if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){450

451 System.out.println("security provider BC not found, will add provider");452

453 Security.addProvider(newBouncyCastleProvider());454

455 }456

457 //生成密钥

458 SecretKey deskey = newSecretKeySpec(keybyte, AlGORITHM);459 //解密

460 Cipher c1 =Cipher.getInstance(AlGORITHM);461 c1.init(Cipher.DECRYPT_MODE, deskey);462 returnc1.doFinal(src);463 } catch(java.security.NoSuchAlgorithmException e1) {464 e1.printStackTrace();465 } catch(javax.crypto.NoSuchPaddingException e2) {466 e2.printStackTrace();467 } catch(java.lang.Exception e3) {468 e3.printStackTrace();469 }470

471 return null;472 }473

474 public static void main(String[] args) throwsJSONException {475

476 InputStream inputStream = null;477 try{478

479 String store_password = "123456";//密钥库密码

480 String password = "123456";//私钥密码

481 String keyAlias = "www.lakala.com";//别名482 //a. 创建针对jks文件的输入流

483

484 inputStream = new FileInputStream("/Users/rinson/eclipse-workspace/lakala/lakala-server/asdc.keystore");//CA 文件名 如: D://tmp/encrypt.jks485 //input = getClass().getClassLoader().getResourceAsStream(keyFile);486 //如果制定classpath下面的证书文件487

488 //b. 创建KeyStore实例 (store_password密钥库密码)

489 KeyStore keyStore = KeyStore.getInstance("JKS");490 keyStore.load(inputStream, store_password.toCharArray());491

492 //c. 获取私钥 (keyAlias 为私钥别名,password为私钥密码)

493 PrivateKey privateKey =(PrivateKey) keyStore.getKey(keyAlias, password.toCharArray());494

495 //实例化一个用SHA算法进行散列,用RSA算法进行加密的Signature.

496 Signature dsa = Signature.getInstance("SHA1withRSA");497 //加载加密散列码用的私钥

498 dsa.initSign(privateKey);499 String param = "XXXXX";500 //进行散列,对产生的散列码进行加密并返回

501 dsa.update(param .getBytes());502

503 System.out.println(Base64.encodeBase64String(dsa.sign()));//进行签名, 加密后的也是二进制的,但是返回给调用方是字符串,将byte[]转为base64编码

504

505 } catch(Exception gse) {506

507 gse.printStackTrace();508

509 } finally{510

511 try{512 if (inputStream != null)513 inputStream.close();//判断

514 } catch(Exception e) {515 e.printStackTrace();516 }517 }518 }519 }

View Code

# PHP

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 <?php2

3 namespace Common\Lib\Lakala;4

5 classSignService6 {7 private $publicPemFilePath='';8 private $privatePemFilePath='';9 private $dESCORPKey = '';10

11 //初始化

12 public function__construct()13 {14 $this->publicPemFilePath = C('lakala_cer_filePath');15 $this->privatePemFilePath=C('lakala_key_filePath');16 $this->dESCORPKey = C('lakala_encrypt_key');17 }18

19 /**20 * 加签并加密需要发送的请求报文21 * @param $head 是java类CommReqHead,需在调用时传入22 * @param $data 是具体的请求参数数组23 *24 */

25 public function ToLakala($head,$data,$url){26 //CommReq

27 $ret =[28 'head'=>$head,

29 'request'=>$data,

30 ];31 $ret = json_encode($ret);32 //会对整个请求body加签加密,返回字符串body体

33 $params = $this->signRequestJsonToSend($ret);34

35 //http request

36 $ch = curl_init($url);37 curl_setopt($ch, CURLOPT_POST, 1);38 curl_setopt($ch, CURLOPT_HEADER, 0);39 curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);40 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);41 curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);42 curl_setopt($ch, CURLOPT_TIMEOUT, 30);43 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json; charset=utf-8', 'Content-Length: ' . strlen($data)));44 curl_setopt($ch, CURLOPT_POSTFIELDS, $params);45 $result = curl_exec($ch);46 curl_close($ch);47 $result = $params;48 //验证返回结果

49 $response = $this->verifyResponseJson($result);50 //结果返回

51 return $response;52 }53

54

55 public functionFromLakala(){56 $lakalaSign = newSignService();57 $params = I('');58 $params = $this->verifyRequestJson($params);59 return $params;60 }61

62 public function FromLakalaResponse($head,$response){63 $ret =[64 'head'=>$head,

65 'response'=>$response,

66 ];67 $res = $this->signResponseJsonToSend($ret);68 return $res;69 }70

71 /**72 * 加签并加密需要发送的请求报文73 * @param $jsonStr 接口报文(String类型)74 * @return 加签后可以发送的json密文75 */

76 public function signRequestJsonToSend($jsonStr)77 {78 if(strpos($jsonStr,"\"head\"")!= false)79 {80 $req = json_decode(trim($jsonStr), true);81 ksort($req);82 //对报文体签名

83 $signData = $this->signData($req['request']);84 $req['head']['signData']= $signData;85 $req['request']=json_encode($req['request'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);86 ksort($req['head']);87 $resultObj = $req;88

89 }90 else if(strpos($jsonStr,"\"comm\"")!= false)91 {92 $req = json_decode(trim($jsonStr), true);93 ksort($req);94 //对报文体签名

95 $signData = $this->signData($req['data']);96 $req['comm']['signtx']=$signData;97 $req['data']=json_encode($req['data'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);98 ksort($req['head']);99 $resultObj = $req;100 }101

102 $signedReq = json_encode($resultObj,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);103

104 //logger.info("加签后的报文:" + signedReq);105 //此处直接放入要加密的数据

106 $key = $this->dESCORPKey;107 $encryptData = self::encrypt($key,$signedReq);108 //logger.info("加密成功,密文:" + encryptStr);

109

110 return $encryptData;111 }112

113 /**114 * 加签并加密需要发送的响应报文115 * @param $jsonStr 接口报文(String类型)116 * @return 加签后可以发送的json密文117 */

118 public function signResponseJsonToSend($jsonStr) {119 if(strpos($jsonStr,"\"head\"")!= false)120 {121 $response = json_decode(trim($jsonStr), true);122 $resultObj = json_decode(json_encode($response));123 }124 else if(strpos($jsonStr,"\"comm\"")!= false)125 {126 $response = json_decode(trim($jsonStr), true);127 ksort($response);128 //对报文体签名

129 $signData = $this->signData($response['data']);130

131 //logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData);

132 $response['comm']['signTx']=$signData;133 $response['data']=json_encode($response['data'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);134 ksort($response['comm']);135 $resultObj = $response;136 }137

138 $signedReq = json_encode($resultObj,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);139 //logger.info("加签后的响应报文:" + signedReq);

140

141 $key = $this->$dESCORPKey;142 $encryptData = self::encrypt($key, $signedReq);143

144 //logger.info("加密成功的响应报文,密文:" + encryptStr);

145 return $encryptData;146 }147

148 /**149 * 对响应的报文json解密,验签, 验签成功则返回data,否则返回null150 * @param json (String)151 * @return (String)152 */

153 public function verifyResponseJson($json) {154 //解密

155 $key = $this->dESCORPKey;156 $decryptBytes = self::decrypt($key, $json);157

158 //logger.info("【收到的响应报文】报文:" + orig);159

160 // 验签

161 $obj = json_decode($decryptBytes);162 $reqStr = json_encode($obj);163 if(strpos($reqStr,"\"comm\"")!= false)164 {165 $response = json_decode($reqStr,true);166 $signtx = $response['comm']['signtx'];167

168 //logger.info("报文中的签名:" + signtx);

169 $nRet = $this->verifyData($response['data'], $signtx);170 if($nRet)171 {172 $response['comm']['signtx']="";173 return json_encode($response);174 }175 else

176 {177 return null;178 }179 }180 else if(strpos($reqStr,"\"head\"")!= false)181 {182 return $reqStr;183 }184 else

185 {186 return null;187 }188 }189

190 /**191 * 对收到的请求json解密,验签, 验签成功则返回data,否则返回null192 * @param json (String)193 * @return (String)194 */

195 public function verifyRequestJson($json) {196 //解密

197 $key = $this->dESCORPKey;198 $decryptBytes = self::decrypt($key, $json);199

200 //验签

201 $obj = json_decode($decryptBytes);202 $reqStr = json_encode($obj);203 if(strpos($reqStr,"\"comm\"")!= false)204 {205 $req = json_decode($reqStr,true);206 ksort($req);207 $signtx = $req['comm']['signtx'];208

209 //logger.info("报文中的签名:" + signtx);

210 $nRet = $this->verifyData($req['data'], $signtx);211 if($nRet)212 {213 $req['comm']['signtx']="";214 return json_encode($req);215 }216 else

217 {218 return null;219 }220 }221 else if(strpos($reqStr,"\"head\"")!= false)222 {223 $req = json_decode($reqStr,true);224 ksort($req);225 $signData = $req['head']['signData'];226 //logger.info("报文中的签名:" + signData);

227 $nRet = $this->verifyData($req['request'], $signData);228 return $nRet;229 if($nRet)230 {231 $req['head']['signData']="";232 return json_encode($req);233 }234 else

235 {236 return null;237 }238 }239 else

240 {241 return null;242 }243 }244 /*使用私钥签名,并返回密文245 * @param param 需要进行签名的数据(Array)246 * @return 签名(加签字符串String)247 */

248 private function signData($param)249 {250 $content = json_encode($param,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);251 $privateKey=openssl_pkey_get_private(file_get_contents($this->privatePemFilePath));252 openssl_sign($content, $signature, $privateKey);253 openssl_free_key($privateKey);254 return base64_encode($signature);255

256 }257

258 /*用公钥证书对字符串进行签名验证259 * @param urlParam 需要进行签名验证的数据(直接从解密报文中取出的String)260 * @param signParam 编码后的签名(直接从解密报文中取出的String)261 * @return 校验签名,true为正确 false为错误262 */

263 private function verifyData($urlParam,$signParam)264 {265 $signature = base64_decode($signParam);266 $pubkeyid = openssl_pkey_get_public(file_get_contents($publicPemFilePath));267 //state whether signature is okay or not

268 $verifies = openssl_verify($urlParam, $signature, $pubkeyid);269 return $verifies;270 }271

272 /**273 * @param $key获取的密钥字符串(直接从配置文件中取出)274 * @param $data (字符串)275 * @return string276 */

277 public function encrypt($key,$data){278 $decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)

279 $encData = openssl_encrypt($data, 'DES-EDE3', $decode_key,OPENSSL_RAW_DATA);280 $encData = base64_encode($encData);281 return $encData;282 }283

284 /**285 * @param $key获取的密钥字符串(直接从配置文件中取出)286 * @param $data (字符串)287 * @return string288 */

289 public function decrypt($key,$data){290 $decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)

291 $data = base64_decode($data);//此处需要BASE64解码(变为2进制)

292 $decData = openssl_decrypt($data, 'DES-EDE3', $decode_key,OPENSSL_RAW_DATA);293 return $decData;294 }295

296 }297

298 ?>

View Code

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值