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

 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 implements Serializable {
2 private static final long serialVersionUID = 1L;
3 private CommReqHead head;
4 private String 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

  1 /**
  2  * 
  3  */
  4 package com.chuangmi.foundation.lakala.service.impl;
  5 
  6 import java.io.BufferedReader;
  7 import java.io.FileInputStream;
  8 import java.io.IOException;
  9 import java.io.InputStream;
 10 import java.io.UnsupportedEncodingException;
 11 import java.security.KeyStore;
 12 import java.security.PrivateKey;
 13 import java.security.PublicKey;
 14 import java.security.Security;
 15 import java.security.Signature;
 16 
 17 import javax.annotation.PostConstruct;
 18 import javax.crypto.Cipher;
 19 import javax.crypto.SecretKey;
 20 import javax.crypto.spec.SecretKeySpec;
 21 import javax.security.cert.X509Certificate;
 22 import javax.servlet.http.HttpServletRequest;
 23 
 24 import org.apache.commons.codec.binary.Base64;
 25 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 26 import org.slf4j.Logger;
 27 import org.slf4j.LoggerFactory;
 28 import org.springframework.beans.factory.annotation.Value;
 29 import org.springframework.stereotype.Service;
 30 
 31 import com.alibaba.fastjson.JSON;
 32 import com.alibaba.fastjson.JSONException;
 33 import com.alibaba.fastjson.JSONObject;
 34 import com.alibaba.fastjson.parser.Feature;
 35 import com.chuangmi.foundation.lakala.service.SignService;
 36 import com.chuangmi.foundation.lakala.service.models.CommReq;
 37 import com.chuangmi.foundation.lakala.service.models.CommRequest;
 38 import com.chuangmi.foundation.lakala.service.models.CommRes;
 39 import com.chuangmi.foundation.lakala.service.models.CommResponse;
 40 
 41 
 42 @Service
 43 public class SignServiceImpl implements SignService {
 44     
 45     private static final Logger logger = LoggerFactory.getLogger(SignService.class);
 46     
 47     @Value("${cer.filePath}")
 48     private String cerFilePath;
 49 
 50     @Value("${key.filePath}")
 51     private String keyFilePath;
 52 
 53     @Value("${key.passWord}")
 54     private String keyPassWord;
 55     
 56     @Value("${key.alias}")
 57     private String alias;
 58     
 59     @Value("${encrypt.key}")
 60     private String dESCORPKey;
 61     
 62     /**
 63      * 加密算法与填充方式
 64      */
 65     public static final String AlGORITHM = "DESede/ECB/PKCS5Padding"; // 定义加密算法,可用
 66     
 67     @PostConstruct   
 68     public void init(){  
 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(new BouncyCastleProvider());
 74 
 75         }
 76     }
 77 
 78     /**
 79      * 加签并加密需要发送的请求报文
 80      * @param 接口报文
 81      * @return 加签后可以发送的json密文
 82      * @throws JSONException 
 83      */
 84     @Override
 85     public String signRequestJsonToSend(String jsonStr) throws JSONException {
 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         return encryptStr;
122     }
123     
124     /**
125      * 加签并加密需要发送的响应报文
126      * @param 接口报文
127      * @return 加签后可以发送的json密文
128      * @throws JSONException 
129      */
130     @Override
131     public String signResponseJsonToSend(String jsonStr) throws JSONException {
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         return encryptStr;
162     }
163 
164     /**
165      * 从request提取json data,并解密,验签, 验签成功则返回data,否则返回null
166      * @param request
167      * @return
168      * @throws IOException
169      * @throws JSONException
170      */
171     @Override
172     public String verifyReceivedRequest(HttpServletRequest request) throws IOException, JSONException {
173         String json = extractJson(request);
174         logger.info("接收报文密文:" + json);
175         
176         return verifyRequestJson(json);
177     }
178 
179     /**
180      * 对收到的请求json解密,验签, 验签成功则返回data,否则返回null
181      * @param json
182      * @return
183      * @throws UnsupportedEncodingException
184      * @throws JSONException
185      */
186     @Override
187     public String verifyRequestJson(String json) throws UnsupportedEncodingException, 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                 return JSON.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                 return JSON.toJSONString(req);
225             }
226             else
227             {
228                 return null;
229             }
230         }
231         else
232         {
233             return null;
234         }
235     }
236     
237     /**
238      * 对响应的报文json解密,验签, 验签成功则返回data,否则返回null
239      * @param json
240      * @return
241      * @throws UnsupportedEncodingException
242      * @throws JSONException
243      */
244     @Override
245     public String verifyResponseJson(String json) throws UnsupportedEncodingException, 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                 return JSON.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             return JSON.toJSONString(response);
276         }
277         else
278         {
279             return null;
280         }
281     }
282     
283     public String extractJson(HttpServletRequest request) throws IOException {
284         //用于接收对方的jsonString
285         StringBuilder jsonString = new StringBuilder();
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         return data;
297     }
298     
299     /*  使用私钥签名,并返回密文
300       * @param param  需要进行签名的数据
301       * @return 签名
302       */
303     private String 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.jks
314             // 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 boolean verifyData(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.p7b
366             // 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         return verifies;
400 
401     }
402     
403     
404     // DES,DESede,Blowfish
405     /**
406      * 使用3des加密明文
407      * 
408      * @param byte[] key: 密钥
409      * @param byte[] 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(new BouncyCastleProvider());
420 
421             }
422             
423             // 生成密钥
424             SecretKey deskey = new SecretKeySpec(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      * @param byte[] key: 密钥
443      * @param byte[] 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(new BouncyCastleProvider());
454 
455             }
456             
457             // 生成密钥
458             SecretKey deskey = new SecretKeySpec(keybyte, AlGORITHM);
459             // 解密
460             Cipher c1 = Cipher.getInstance(AlGORITHM);
461             c1.init(Cipher.DECRYPT_MODE, deskey);
462             return c1.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) throws JSONException {
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.jks
485             // 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

  1 <?php
  2 
  3 namespace Common\Lib\Lakala;
  4 
  5 class SignService
  6 {
  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 function FromLakala(){
 56         $lakalaSign = new SignService();
 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,否则返回null
150      * @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,否则返回null
192      * @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 string
276      */
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 string
288      */
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

 

posted @ 2018-07-18 18:28  rinson  阅读(4209)  评论(1编辑  收藏  举报