手记

记一次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     @Override131     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,否则返回null166      * @param request167      * @return168      * @throws IOException169      * @throws JSONException170      */171     @Override172     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,否则返回null181      * @param json182      * @return183      * @throws UnsupportedEncodingException184      * @throws JSONException185      */186     @Override187     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             else210             {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             else227             {228                 return null;229             }230         }231         else232         {233             return null;234         }235     }236     237     /**238      * 对响应的报文json解密,验签, 验签成功则返回data,否则返回null239      * @param json240      * @return241      * @throws UnsupportedEncodingException242      * @throws JSONException243      */244     @Override245     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             else268             {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         else278         {279             return null;280         }281     }282     283     public String extractJson(HttpServletRequest request) throws IOException {284         //用于接收对方的jsonString285         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.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 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.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         return verifies;400 401     }402     403     404     // DES,DESede,Blowfish405     /**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.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 }

 

# 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,否则返回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             else176             {177                 return null;178             }179         }180         else if(strpos($reqStr,"\"head\"")!= false)181         {182             return $reqStr;183         }184         else185         {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             else217             {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             else235             {236                 return null;237             }238         }239         else240         {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 not268         $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 ?>

原文出处


0人推荐
随时随地看视频
慕课网APP