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

博主: Simon Lin 创建于: Jul 18, 2018 更新于: Jul 18, 2018
分类: tech
标签: tech language

记一次Java加密加签算法到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转换,如果你送入了错误的,会永远困在迷惘中。

1
2
3
4
5
6
7
8
9
10
$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):

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{ 
"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
2
3
4
5
public class CommReq implements Serializable {
private static final long serialVersionUID = 1L;
private CommReqHead head;
private String request;
}

JAVA这种强类型语言,必须定义类型,将request定义成String型,导致JSON解析出来的结果:

1
"request":"{\"userId\":\"40012345678\",\"userName\": \"AA\", \"userMobile\": \"18675529912\", \"idNo\": \"110101198609096078\" } ";

而PHP是弱类型语言,直接嵌套进去也解析出来了

1
"request": {"userId":"40012345678","userName": "AA", "userMobile": "18675529912", "idNo": "110101198609096078" } }

如果PHP要和JAVA真正保持一致(因为一旦不一致,加密结果就不一样了)

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

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

小结

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

JAVA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
/**
*
*/
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
<?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;
}

}

?>

打赏 支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者